mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-10 10:00:46 +09:00
GO-3964 Merge main
This commit is contained in:
commit
708b51e273
47 changed files with 1048 additions and 269 deletions
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
# The script below adds the branch name automatically to
|
||||
# every one of your commit messages. The regular expression
|
||||
# below searches for linear issue key's. The issue key will
|
||||
|
@ -24,4 +24,4 @@ fi
|
|||
|
||||
if [[ "$COMMIT_TEXT" != "GO-"* ]]; then
|
||||
echo "$ISSUE_ID" "$COMMIT_TEXT" > "$COMMIT_MSG_FILE"
|
||||
fi
|
||||
fi
|
||||
|
|
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
|
@ -129,7 +129,6 @@ jobs:
|
|||
make protos-server
|
||||
CC="x86_64-w64-mingw32-gcc" CXX="x86_64-w64-mingw32-g++" gox -cgo -ldflags="$FLAGS -linkmode external -extldflags=-static" -osarch="windows/amd64" --tags="envproduction nographviz nowatchdog nosigar nomutexdeadlockdetector noheic" -output="{{.OS}}-{{.Arch}}" github.com/anyproto/anytype-heart/cmd/grpcserver
|
||||
ls -lha .
|
||||
if: github.event_name == 'push'
|
||||
- name: run perf tests
|
||||
run: |
|
||||
echo "Running perf tests"
|
||||
|
|
8
Makefile
8
Makefile
|
@ -260,7 +260,7 @@ protos-swift:
|
|||
@echo 'Generating swift protobuf files'
|
||||
@protoc -I ./ --swift_opt=FileNaming=DropPath --swift_opt=Visibility=Public --swift_out=./dist/ios/protobuf pb/protos/*.proto pkg/lib/pb/model/protos/*.proto
|
||||
@echo 'Generated swift protobuf files at ./dist/ios/pb'
|
||||
|
||||
|
||||
protos-swift-local: protos-swift
|
||||
@echo 'Clear proto files'
|
||||
@rm -rf ./dist/ios/protobuf/protos
|
||||
|
@ -325,14 +325,14 @@ install-linter:
|
|||
run-linter:
|
||||
ifdef GOLANGCI_LINT_BRANCH
|
||||
@golangci-lint run -v ./... --new-from-rev=$(GOLANGCI_LINT_BRANCH) --timeout 15m --verbose
|
||||
else
|
||||
else
|
||||
@golangci-lint run -v ./... --new-from-rev=origin/main --timeout 15m --verbose
|
||||
endif
|
||||
|
||||
run-linter-fix:
|
||||
ifdef GOLANGCI_LINT_BRANCH
|
||||
@golangci-lint run -v ./... --new-from-rev=$(GOLANGCI_LINT_BRANCH) --timeout 15m --fix
|
||||
else
|
||||
else
|
||||
@golangci-lint run -v ./... --new-from-rev=origin/main --timeout 15m --fix
|
||||
endif
|
||||
|
||||
|
@ -390,4 +390,4 @@ download-tantivy-all: download-tantivy
|
|||
|
||||
download-tantivy-local: remove-libs
|
||||
@mkdir -p $(OUTPUT_DIR)
|
||||
@cp -r $(TANTIVY_GO_PATH)/libs/ $(OUTPUT_DIR)
|
||||
@cp -r $(TANTIVY_GO_PATH)/libs/* $(OUTPUT_DIR)
|
||||
|
|
|
@ -9,9 +9,10 @@ Middleware library for Anytype, distributed as part of the Anytype clients.
|
|||
- [Project architecture](docs/Architecture.md)
|
||||
- [Style guide](docs/Codestyle.md)
|
||||
- [Project workflows](docs/Flow.md)
|
||||
- [Complex filters](docs/ComplexFilters.md)
|
||||
|
||||
### CLI tools
|
||||
- [Usecase generator](docs/UsecaseGenerator.md)
|
||||
- [Usecase validator](docs/UsecaseValidator.md)
|
||||
- [Export Archive unpacker](docs/ExportArchiveUnpacker.md)
|
||||
|
||||
## Contribution
|
||||
|
|
|
@ -41,6 +41,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/block/details"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/converter"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/lastused"
|
||||
"github.com/anyproto/anytype-heart/core/block/export"
|
||||
importer "github.com/anyproto/anytype-heart/core/block/import"
|
||||
"github.com/anyproto/anytype-heart/core/block/object/idresolver"
|
||||
|
@ -303,7 +304,8 @@ func Bootstrap(a *app.App, components ...app.Component) {
|
|||
Register(nameserviceclient.New()).
|
||||
Register(payments.New()).
|
||||
Register(paymentscache.New()).
|
||||
Register(peerstatus.New())
|
||||
Register(peerstatus.New()).
|
||||
Register(lastused.New())
|
||||
}
|
||||
|
||||
func MiddlewareVersion() string {
|
||||
|
|
|
@ -35,6 +35,7 @@ type Service interface {
|
|||
cache.ObjectGetterComponent
|
||||
|
||||
SetDetails(ctx session.Context, objectId string, details []*model.Detail) error
|
||||
SetDetailsAndUpdateLastUsed(ctx session.Context, objectId string, details []*model.Detail) error
|
||||
SetDetailsList(ctx session.Context, objectIds []string, details []*model.Detail) error
|
||||
ModifyDetails(objectId string, modifier func(current *types.Struct) (*types.Struct, error)) error
|
||||
ModifyDetailsList(req *pb.RpcObjectListModifyDetailValuesRequest) error
|
||||
|
@ -99,13 +100,23 @@ func (s *service) SetDetails(ctx session.Context, objectId string, details []*mo
|
|||
})
|
||||
}
|
||||
|
||||
func (s *service) SetDetailsAndUpdateLastUsed(ctx session.Context, objectId string, details []*model.Detail) (err error) {
|
||||
return cache.Do(s, objectId, func(b basic.DetailsSettable) error {
|
||||
return b.SetDetailsAndUpdateLastUsed(ctx, details, true)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *service) SetDetailsList(ctx session.Context, objectIds []string, details []*model.Detail) (err error) {
|
||||
var (
|
||||
resultError error
|
||||
anySucceed bool
|
||||
)
|
||||
for _, objectId := range objectIds {
|
||||
err := s.SetDetails(ctx, objectId, details)
|
||||
for i, objectId := range objectIds {
|
||||
setDetailsFunc := s.SetDetails
|
||||
if i == 0 {
|
||||
setDetailsFunc = s.SetDetailsAndUpdateLastUsed
|
||||
}
|
||||
err := setDetailsFunc(ctx, objectId, details)
|
||||
if err != nil {
|
||||
resultError = errors.Join(resultError, err)
|
||||
} else {
|
||||
|
@ -128,10 +139,20 @@ func (s *service) ModifyDetails(objectId string, modifier func(current *types.St
|
|||
})
|
||||
}
|
||||
|
||||
func (s *service) ModifyDetailsAndUpdateLastUsed(objectId string, modifier func(current *types.Struct) (*types.Struct, error)) (err error) {
|
||||
return cache.Do(s, objectId, func(du basic.DetailsUpdatable) error {
|
||||
return du.UpdateDetailsAndLastUsed(modifier)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *service) ModifyDetailsList(req *pb.RpcObjectListModifyDetailValuesRequest) (resultError error) {
|
||||
var anySucceed bool
|
||||
for _, objectId := range req.ObjectIds {
|
||||
err := s.ModifyDetails(objectId, func(current *types.Struct) (*types.Struct, error) {
|
||||
for i, objectId := range req.ObjectIds {
|
||||
modifyDetailsFunc := s.ModifyDetails
|
||||
if i == 0 {
|
||||
modifyDetailsFunc = s.ModifyDetailsAndUpdateLastUsed
|
||||
}
|
||||
err := modifyDetailsFunc(objectId, func(current *types.Struct) (*types.Struct, error) {
|
||||
for _, op := range req.Operations {
|
||||
if !pbtypes.IsEmptyValue(op.Set) {
|
||||
// Set operation has higher priority than Add and Remove, because it modifies full value
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/samber/lo"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/converter"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/lastused"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/table"
|
||||
|
@ -65,10 +66,12 @@ type CommonOperations interface {
|
|||
|
||||
type DetailsSettable interface {
|
||||
SetDetails(ctx session.Context, details []*model.Detail, showEvent bool) (err error)
|
||||
SetDetailsAndUpdateLastUsed(ctx session.Context, details []*model.Detail, showEvent bool) (err error)
|
||||
}
|
||||
|
||||
type DetailsUpdatable interface {
|
||||
UpdateDetails(update func(current *types.Struct) (*types.Struct, error)) (err error)
|
||||
UpdateDetailsAndLastUsed(update func(current *types.Struct) (*types.Struct, error)) (err error)
|
||||
}
|
||||
|
||||
type Restrictionable interface {
|
||||
|
@ -104,12 +107,14 @@ func NewBasic(
|
|||
objectStore objectstore.ObjectStore,
|
||||
layoutConverter converter.LayoutConverter,
|
||||
fileObjectService fileobject.Service,
|
||||
lastUsedUpdater lastused.ObjectUsageUpdater,
|
||||
) AllOperations {
|
||||
return &basic{
|
||||
SmartBlock: sb,
|
||||
objectStore: objectStore,
|
||||
layoutConverter: layoutConverter,
|
||||
fileObjectService: fileObjectService,
|
||||
lastUsedUpdater: lastUsedUpdater,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,6 +124,7 @@ type basic struct {
|
|||
objectStore objectstore.ObjectStore
|
||||
layoutConverter converter.LayoutConverter
|
||||
fileObjectService fileobject.Service
|
||||
lastUsedUpdater lastused.ObjectUsageUpdater
|
||||
}
|
||||
|
||||
func (bs *basic) CreateBlock(s *state.State, req pb.RpcBlockCreateRequest) (id string, err error) {
|
||||
|
|
|
@ -45,7 +45,7 @@ func TestBasic_Create(t *testing.T) {
|
|||
t.Run("generic", func(t *testing.T) {
|
||||
sb := smarttest.New("test")
|
||||
sb.AddBlock(simple.New(&model.Block{Id: "test"}))
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
id, err := b.CreateBlock(st, pb.RpcBlockCreateRequest{
|
||||
Block: &model.Block{Content: &model.BlockContentOfText{Text: &model.BlockContentText{Text: "ll"}}},
|
||||
|
@ -59,7 +59,7 @@ func TestBasic_Create(t *testing.T) {
|
|||
sb := smarttest.New("test")
|
||||
sb.AddBlock(simple.New(&model.Block{Id: "test"}))
|
||||
require.NoError(t, smartblock.ObjectApplyTemplate(sb, sb.NewState(), template.WithTitle))
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
s := sb.NewState()
|
||||
id, err := b.CreateBlock(s, pb.RpcBlockCreateRequest{
|
||||
TargetId: template.TitleBlockId,
|
||||
|
@ -80,7 +80,7 @@ func TestBasic_Create(t *testing.T) {
|
|||
}
|
||||
sb.AddBlock(simple.New(&model.Block{Id: "test"}))
|
||||
require.NoError(t, smartblock.ObjectApplyTemplate(sb, sb.NewState(), template.WithTitle))
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
_, err := b.CreateBlock(sb.NewState(), pb.RpcBlockCreateRequest{})
|
||||
assert.ErrorIs(t, err, restriction.ErrRestricted)
|
||||
})
|
||||
|
@ -94,7 +94,7 @@ func TestBasic_Duplicate(t *testing.T) {
|
|||
AddBlock(simple.New(&model.Block{Id: "3"}))
|
||||
|
||||
st := sb.NewState()
|
||||
newIds, err := NewBasic(sb, nil, converter.NewLayoutConverter(), nil).Duplicate(st, st, "", 0, []string{"2"})
|
||||
newIds, err := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil).Duplicate(st, st, "", 0, []string{"2"})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = sb.Apply(st)
|
||||
|
@ -172,7 +172,7 @@ func TestBasic_Duplicate(t *testing.T) {
|
|||
ts.SetDetail(bundle.RelationKeySpaceId.String(), pbtypes.String(tc.spaceIds[1]))
|
||||
|
||||
// when
|
||||
newIds, err := NewBasic(source, nil, nil, tc.fos()).Duplicate(ss, ts, "target", model.Block_Inner, []string{"1", "f1"})
|
||||
newIds, err := NewBasic(source, nil, nil, tc.fos(), nil).Duplicate(ss, ts, "target", model.Block_Inner, []string{"1", "f1"})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, target.Apply(ts))
|
||||
|
||||
|
@ -206,7 +206,7 @@ func TestBasic_Unlink(t *testing.T) {
|
|||
AddBlock(simple.New(&model.Block{Id: "2", ChildrenIds: []string{"3"}})).
|
||||
AddBlock(simple.New(&model.Block{Id: "3"}))
|
||||
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
|
||||
err := b.Unlink(nil, "2")
|
||||
require.NoError(t, err)
|
||||
|
@ -220,7 +220,7 @@ func TestBasic_Unlink(t *testing.T) {
|
|||
AddBlock(simple.New(&model.Block{Id: "2", ChildrenIds: []string{"3"}})).
|
||||
AddBlock(simple.New(&model.Block{Id: "3"}))
|
||||
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
|
||||
err := b.Unlink(nil, "2", "3")
|
||||
require.NoError(t, err)
|
||||
|
@ -237,7 +237,7 @@ func TestBasic_Move(t *testing.T) {
|
|||
AddBlock(simple.New(&model.Block{Id: "3"})).
|
||||
AddBlock(simple.New(&model.Block{Id: "4"}))
|
||||
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
|
||||
err := b.Move(st, st, "4", model.Block_Inner, []string{"3"})
|
||||
|
@ -251,7 +251,7 @@ func TestBasic_Move(t *testing.T) {
|
|||
sb := smarttest.New("test")
|
||||
sb.AddBlock(simple.New(&model.Block{Id: "test"}))
|
||||
require.NoError(t, smartblock.ObjectApplyTemplate(sb, sb.NewState(), template.WithTitle))
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
s := sb.NewState()
|
||||
id1, err := b.CreateBlock(s, pb.RpcBlockCreateRequest{
|
||||
TargetId: template.HeaderLayoutId,
|
||||
|
@ -300,7 +300,7 @@ func TestBasic_Move(t *testing.T) {
|
|||
},
|
||||
),
|
||||
)
|
||||
basic := NewBasic(testDoc, nil, converter.NewLayoutConverter(), nil)
|
||||
basic := NewBasic(testDoc, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
state := testDoc.NewState()
|
||||
|
||||
// when
|
||||
|
@ -316,7 +316,7 @@ func TestBasic_Move(t *testing.T) {
|
|||
AddBlock(newTextBlock("1", "", nil)).
|
||||
AddBlock(newTextBlock("2", "one", nil))
|
||||
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
err := b.Move(st, st, "1", model.Block_InnerFirst, []string{"2"})
|
||||
require.NoError(t, err)
|
||||
|
@ -336,7 +336,7 @@ func TestBasic_Move(t *testing.T) {
|
|||
AddBlock(firstBlock).
|
||||
AddBlock(secondBlock)
|
||||
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
err := b.Move(st, st, "1", model.Block_InnerFirst, []string{"2"})
|
||||
require.NoError(t, err)
|
||||
|
@ -350,7 +350,7 @@ func TestBasic_Move(t *testing.T) {
|
|||
AddBlock(newTextBlock("1", "", nil)).
|
||||
AddBlock(newTextBlock("2", "one", nil))
|
||||
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
err := b.Move(st, nil, "1", model.Block_Top, []string{"2"})
|
||||
require.NoError(t, err)
|
||||
|
@ -379,7 +379,7 @@ func TestBasic_MoveTableBlocks(t *testing.T) {
|
|||
t.Run("moving non-root table block '"+block+"' leads to error", func(t *testing.T) {
|
||||
// given
|
||||
sb := getSB()
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
|
||||
// when
|
||||
|
@ -394,7 +394,7 @@ func TestBasic_MoveTableBlocks(t *testing.T) {
|
|||
t.Run("no error on moving root table block", func(t *testing.T) {
|
||||
// given
|
||||
sb := getSB()
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
|
||||
// when
|
||||
|
@ -408,7 +408,7 @@ func TestBasic_MoveTableBlocks(t *testing.T) {
|
|||
t.Run("no error on moving one row between another", func(t *testing.T) {
|
||||
// given
|
||||
sb := getSB()
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
|
||||
// when
|
||||
|
@ -422,7 +422,7 @@ func TestBasic_MoveTableBlocks(t *testing.T) {
|
|||
t.Run("moving rows with incorrect position leads to error", func(t *testing.T) {
|
||||
// given
|
||||
sb := getSB()
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
|
||||
// when
|
||||
|
@ -435,7 +435,7 @@ func TestBasic_MoveTableBlocks(t *testing.T) {
|
|||
t.Run("moving rows and some other blocks between another leads to error", func(t *testing.T) {
|
||||
// given
|
||||
sb := getSB()
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
|
||||
// when
|
||||
|
@ -448,7 +448,7 @@ func TestBasic_MoveTableBlocks(t *testing.T) {
|
|||
t.Run("moving the row between itself leads to error", func(t *testing.T) {
|
||||
// given
|
||||
sb := getSB()
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
|
||||
// when
|
||||
|
@ -461,7 +461,7 @@ func TestBasic_MoveTableBlocks(t *testing.T) {
|
|||
t.Run("moving table block from invalid table leads to error", func(t *testing.T) {
|
||||
// given
|
||||
sb := getSB()
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
st.Unlink("columns")
|
||||
|
||||
|
@ -477,7 +477,7 @@ func TestBasic_MoveTableBlocks(t *testing.T) {
|
|||
t.Run("moving a block to '"+block+"' block leads to moving it under the table", func(t *testing.T) {
|
||||
// given
|
||||
sb := getSB()
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
|
||||
// when
|
||||
|
@ -492,7 +492,7 @@ func TestBasic_MoveTableBlocks(t *testing.T) {
|
|||
t.Run("moving a block to the invalid table leads to moving it under the table", func(t *testing.T) {
|
||||
// given
|
||||
sb := getSB()
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
st := sb.NewState()
|
||||
st.Unlink("columns")
|
||||
|
||||
|
@ -516,7 +516,7 @@ func TestBasic_MoveToAnotherObject(t *testing.T) {
|
|||
sb2 := smarttest.New("test2")
|
||||
sb2.AddBlock(simple.New(&model.Block{Id: "test2", ChildrenIds: []string{}}))
|
||||
|
||||
b := NewBasic(sb1, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb1, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
|
||||
srcState := sb1.NewState()
|
||||
destState := sb2.NewState()
|
||||
|
@ -551,7 +551,7 @@ func TestBasic_Replace(t *testing.T) {
|
|||
sb := smarttest.New("test")
|
||||
sb.AddBlock(simple.New(&model.Block{Id: "test", ChildrenIds: []string{"2"}})).
|
||||
AddBlock(simple.New(&model.Block{Id: "2"}))
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
newId, err := b.Replace(nil, "2", &model.Block{Content: &model.BlockContentOfText{Text: &model.BlockContentText{Text: "l"}}})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, newId)
|
||||
|
@ -561,7 +561,7 @@ func TestBasic_SetFields(t *testing.T) {
|
|||
sb := smarttest.New("test")
|
||||
sb.AddBlock(simple.New(&model.Block{Id: "test", ChildrenIds: []string{"2"}})).
|
||||
AddBlock(simple.New(&model.Block{Id: "2"}))
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
|
||||
fields := &types.Struct{
|
||||
Fields: map[string]*types.Value{
|
||||
|
@ -580,7 +580,7 @@ func TestBasic_Update(t *testing.T) {
|
|||
sb := smarttest.New("test")
|
||||
sb.AddBlock(simple.New(&model.Block{Id: "test", ChildrenIds: []string{"2"}})).
|
||||
AddBlock(simple.New(&model.Block{Id: "2"}))
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
|
||||
err := b.Update(nil, func(b simple.Block) error {
|
||||
b.Model().BackgroundColor = "test"
|
||||
|
@ -594,7 +594,7 @@ func TestBasic_SetDivStyle(t *testing.T) {
|
|||
sb := smarttest.New("test")
|
||||
sb.AddBlock(simple.New(&model.Block{Id: "test", ChildrenIds: []string{"2"}})).
|
||||
AddBlock(simple.New(&model.Block{Id: "2", Content: &model.BlockContentOfDiv{Div: &model.BlockContentDiv{}}}))
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
|
||||
err := b.SetDivStyle(nil, model.BlockContentDiv_Dots, "2")
|
||||
require.NoError(t, err)
|
||||
|
@ -614,7 +614,7 @@ func TestBasic_SetRelationKey(t *testing.T) {
|
|||
t.Run("correct", func(t *testing.T) {
|
||||
sb := smarttest.New("test")
|
||||
fillSb(sb)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
err := b.SetRelationKey(nil, pb.RpcBlockRelationSetKeyRequest{
|
||||
BlockId: "2",
|
||||
Key: "testRelKey",
|
||||
|
@ -636,7 +636,7 @@ func TestBasic_SetRelationKey(t *testing.T) {
|
|||
t.Run("not relation block", func(t *testing.T) {
|
||||
sb := smarttest.New("test")
|
||||
fillSb(sb)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
require.Error(t, b.SetRelationKey(nil, pb.RpcBlockRelationSetKeyRequest{
|
||||
BlockId: "1",
|
||||
Key: "key",
|
||||
|
@ -645,7 +645,7 @@ func TestBasic_SetRelationKey(t *testing.T) {
|
|||
t.Run("relation not found", func(t *testing.T) {
|
||||
sb := smarttest.New("test")
|
||||
fillSb(sb)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
require.Error(t, b.SetRelationKey(nil, pb.RpcBlockRelationSetKeyRequest{
|
||||
BlockId: "2",
|
||||
Key: "not exists",
|
||||
|
@ -661,7 +661,7 @@ func TestBasic_FeaturedRelationAdd(t *testing.T) {
|
|||
s.AddBundledRelationLinks(bundle.RelationKeyDescription)
|
||||
require.NoError(t, sb.Apply(s))
|
||||
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
newRel := []string{bundle.RelationKeyDescription.String(), bundle.RelationKeyName.String()}
|
||||
require.NoError(t, b.FeaturedRelationAdd(nil, newRel...))
|
||||
|
||||
|
@ -677,7 +677,7 @@ func TestBasic_FeaturedRelationRemove(t *testing.T) {
|
|||
template.WithDescription(s)
|
||||
require.NoError(t, sb.Apply(s))
|
||||
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
require.NoError(t, b.FeaturedRelationRemove(nil, bundle.RelationKeyDescription.String()))
|
||||
|
||||
res := sb.NewState()
|
||||
|
@ -714,7 +714,7 @@ func TestBasic_ReplaceLink(t *testing.T) {
|
|||
}
|
||||
require.NoError(t, sb.Apply(s))
|
||||
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, nil, converter.NewLayoutConverter(), nil, nil)
|
||||
require.NoError(t, b.ReplaceLink(oldId, newId))
|
||||
|
||||
res := sb.NewState()
|
||||
|
|
|
@ -4,11 +4,11 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/objecttype"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/block/restriction"
|
||||
|
@ -33,11 +33,29 @@ type detailUpdate struct {
|
|||
}
|
||||
|
||||
func (bs *basic) SetDetails(ctx session.Context, details []*model.Detail, showEvent bool) (err error) {
|
||||
_, err = bs.setDetails(ctx, details, showEvent)
|
||||
return err
|
||||
}
|
||||
|
||||
func (bs *basic) SetDetailsAndUpdateLastUsed(ctx session.Context, details []*model.Detail, showEvent bool) (err error) {
|
||||
var keys []domain.RelationKey
|
||||
keys, err = bs.setDetails(ctx, details, showEvent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ts := time.Now().Unix()
|
||||
for _, key := range keys {
|
||||
bs.lastUsedUpdater.UpdateLastUsedDate(bs.SpaceID(), key, ts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bs *basic) setDetails(ctx session.Context, details []*model.Detail, showEvent bool) (updatedKeys []domain.RelationKey, err error) {
|
||||
s := bs.NewStateCtx(ctx)
|
||||
|
||||
// Collect updates handling special cases. These cases could update details themselves, so we
|
||||
// have to apply changes later
|
||||
updates := bs.collectDetailUpdates(details, s)
|
||||
updates, updatedKeys := bs.collectDetailUpdates(details, s)
|
||||
newDetails := applyDetailUpdates(s.CombinedDetails(), updates)
|
||||
s.SetDetails(newDetails)
|
||||
|
||||
|
@ -46,43 +64,71 @@ func (bs *basic) SetDetails(ctx session.Context, details []*model.Detail, showEv
|
|||
flags.AddToState(s)
|
||||
|
||||
if err = bs.Apply(s, smartblock.NoRestrictions, smartblock.KeepInternalFlags); err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bs.discardOwnSetDetailsEvent(ctx, showEvent)
|
||||
return nil
|
||||
return updatedKeys, nil
|
||||
}
|
||||
|
||||
func (bs *basic) UpdateDetails(update func(current *types.Struct) (*types.Struct, error)) (err error) {
|
||||
_, _, err = bs.updateDetails(update)
|
||||
return err
|
||||
}
|
||||
|
||||
func (bs *basic) UpdateDetailsAndLastUsed(update func(current *types.Struct) (*types.Struct, error)) (err error) {
|
||||
var oldDetails, newDetails *types.Struct
|
||||
oldDetails, newDetails, err = bs.updateDetails(update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
diff := pbtypes.StructDiff(oldDetails, newDetails)
|
||||
if diff == nil || diff.Fields == nil {
|
||||
return nil
|
||||
}
|
||||
ts := time.Now().Unix()
|
||||
for key := range diff.Fields {
|
||||
bs.lastUsedUpdater.UpdateLastUsedDate(bs.SpaceID(), domain.RelationKey(key), ts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bs *basic) updateDetails(update func(current *types.Struct) (*types.Struct, error)) (oldDetails, newDetails *types.Struct, err error) {
|
||||
if update == nil {
|
||||
return fmt.Errorf("update function is nil")
|
||||
return nil, nil, fmt.Errorf("update function is nil")
|
||||
}
|
||||
s := bs.NewState()
|
||||
|
||||
newDetails, err := update(s.CombinedDetails())
|
||||
oldDetails = s.CombinedDetails()
|
||||
oldDetailsCopy := pbtypes.CopyStruct(oldDetails, true)
|
||||
|
||||
newDetails, err = update(oldDetailsCopy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.SetDetails(newDetails)
|
||||
|
||||
if err = bs.addRelationLinks(s, maps.Keys(newDetails.Fields)...); err != nil {
|
||||
return
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return bs.Apply(s)
|
||||
return oldDetails, newDetails, bs.Apply(s)
|
||||
}
|
||||
|
||||
func (bs *basic) collectDetailUpdates(details []*model.Detail, s *state.State) []*detailUpdate {
|
||||
func (bs *basic) collectDetailUpdates(details []*model.Detail, s *state.State) ([]*detailUpdate, []domain.RelationKey) {
|
||||
updates := make([]*detailUpdate, 0, len(details))
|
||||
keys := make([]domain.RelationKey, 0, len(details))
|
||||
for _, detail := range details {
|
||||
update, err := bs.createDetailUpdate(s, detail)
|
||||
if err == nil {
|
||||
updates = append(updates, update)
|
||||
keys = append(keys, domain.RelationKey(update.key))
|
||||
} else {
|
||||
log.Errorf("can't set detail %s: %s", detail.Key, err)
|
||||
}
|
||||
}
|
||||
return updates
|
||||
return updates, keys
|
||||
}
|
||||
|
||||
func applyDetailUpdates(oldDetails *types.Struct, updates []*detailUpdate) *types.Struct {
|
||||
|
@ -342,7 +388,7 @@ func (bs *basic) SetObjectTypesInState(s *state.State, objectTypeKeys []domain.T
|
|||
removeInternalFlags(s)
|
||||
|
||||
if pbtypes.GetInt64(bs.CombinedDetails(), bundle.RelationKeyOrigin.String()) == int64(model.ObjectOrigin_none) {
|
||||
objecttype.UpdateLastUsedDate(bs.Space(), bs.objectStore, objectTypeKeys[0])
|
||||
bs.lastUsedUpdater.UpdateLastUsedDate(bs.SpaceID(), objectTypeKeys[0], time.Now().Unix())
|
||||
}
|
||||
|
||||
toLayout, err := bs.getLayoutForType(objectTypeKeys[0])
|
||||
|
|
|
@ -32,7 +32,7 @@ func newDUFixture(t *testing.T) *duFixture {
|
|||
|
||||
store := objectstore.NewStoreFixture(t)
|
||||
|
||||
b := NewBasic(sb, store, converter.NewLayoutConverter(), nil)
|
||||
b := NewBasic(sb, store, converter.NewLayoutConverter(), nil, nil)
|
||||
|
||||
return &duFixture{
|
||||
sb: sb,
|
||||
|
|
|
@ -323,7 +323,7 @@ func TestExtractObjects(t *testing.T) {
|
|||
ObjectTypeUniqueKey: domain.MustUniqueKey(coresb.SmartBlockTypeObjectType, tc.typeKey).Marshal(),
|
||||
}
|
||||
ctx := session.NewContext()
|
||||
linkIds, err := NewBasic(sb, fixture.store, converter.NewLayoutConverter(), nil).ExtractBlocksToObjects(ctx, creator, ts, req)
|
||||
linkIds, err := NewBasic(sb, fixture.store, converter.NewLayoutConverter(), nil, nil).ExtractBlocksToObjects(ctx, creator, ts, req)
|
||||
assert.NoError(t, err)
|
||||
|
||||
gotBlockIds := []string{}
|
||||
|
@ -378,7 +378,7 @@ func TestExtractObjects(t *testing.T) {
|
|||
}},
|
||||
}
|
||||
ctx := session.NewContext()
|
||||
_, err := NewBasic(sb, fixture.store, converter.NewLayoutConverter(), nil).ExtractBlocksToObjects(ctx, creator, ts, req)
|
||||
_, err := NewBasic(sb, fixture.store, converter.NewLayoutConverter(), nil, nil).ExtractBlocksToObjects(ctx, creator, ts, req)
|
||||
assert.NoError(t, err)
|
||||
var block *model.Block
|
||||
for _, block = range sb.Blocks() {
|
||||
|
@ -411,7 +411,7 @@ func TestExtractObjects(t *testing.T) {
|
|||
}},
|
||||
}
|
||||
ctx := session.NewContext()
|
||||
_, err := NewBasic(sb, fixture.store, converter.NewLayoutConverter(), nil).ExtractBlocksToObjects(ctx, creator, ts, req)
|
||||
_, err := NewBasic(sb, fixture.store, converter.NewLayoutConverter(), nil, nil).ExtractBlocksToObjects(ctx, creator, ts, req)
|
||||
assert.NoError(t, err)
|
||||
var addedBlocks []*model.Block
|
||||
for _, message := range sb.Results.Events {
|
||||
|
|
|
@ -33,7 +33,7 @@ type Dashboard struct {
|
|||
func NewDashboard(sb smartblock.SmartBlock, objectStore objectstore.ObjectStore, layoutConverter converter.LayoutConverter) *Dashboard {
|
||||
return &Dashboard{
|
||||
SmartBlock: sb,
|
||||
AllOperations: basic.NewBasic(sb, objectStore, layoutConverter, nil),
|
||||
AllOperations: basic.NewBasic(sb, objectStore, layoutConverter, nil, nil),
|
||||
Collection: collection.NewCollection(sb, objectStore),
|
||||
objectStore: objectStore,
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/block/editor/bookmark"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/converter"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/file"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/lastused"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/migration"
|
||||
"github.com/anyproto/anytype-heart/core/block/process"
|
||||
|
@ -66,6 +67,7 @@ type ObjectFactory struct {
|
|||
fileReconciler reconciler.Reconciler
|
||||
objectDeleter ObjectDeleter
|
||||
deviceService deviceService
|
||||
lastUsedUpdater lastused.ObjectUsageUpdater
|
||||
}
|
||||
|
||||
func NewObjectFactory() *ObjectFactory {
|
||||
|
@ -94,6 +96,7 @@ func (f *ObjectFactory) Init(a *app.App) (err error) {
|
|||
f.objectDeleter = app.MustComponent[ObjectDeleter](a)
|
||||
f.fileReconciler = app.MustComponent[reconciler.Reconciler](a)
|
||||
f.deviceService = app.MustComponent[deviceService](a)
|
||||
f.lastUsedUpdater = app.MustComponent[lastused.ObjectUsageUpdater](a)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ var fileRequiredRelations = append(pageRequiredRelations, []domain.RelationKey{
|
|||
}...)
|
||||
|
||||
func (f *ObjectFactory) newFile(sb smartblock.SmartBlock) *File {
|
||||
basicComponent := basic.NewBasic(sb, f.objectStore, f.layoutConverter, f.fileObjectService)
|
||||
basicComponent := basic.NewBasic(sb, f.objectStore, f.layoutConverter, f.fileObjectService, f.lastUsedUpdater)
|
||||
return &File{
|
||||
SmartBlock: sb,
|
||||
ChangeReceiver: sb.(source.ChangeReceiver),
|
||||
|
|
216
core/block/editor/lastused/lastused.go
Normal file
216
core/block/editor/lastused/lastused.go
Normal file
|
@ -0,0 +1,216 @@
|
|||
package lastused
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/cheggaaa/mb/v3"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
coresb "github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
"github.com/anyproto/anytype-heart/space"
|
||||
"github.com/anyproto/anytype-heart/space/clientspace"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
const (
|
||||
CName = "object-usage-updater"
|
||||
|
||||
maxInstallationTime = 5 * time.Minute
|
||||
updateInterval = 5 * time.Second
|
||||
)
|
||||
|
||||
type Key interface {
|
||||
URL() string
|
||||
String() string
|
||||
}
|
||||
|
||||
type message struct {
|
||||
spaceId string
|
||||
key Key
|
||||
time int64
|
||||
}
|
||||
|
||||
var log = logger.NewNamed("update-last-used-date")
|
||||
|
||||
type ObjectUsageUpdater interface {
|
||||
app.ComponentRunnable
|
||||
|
||||
UpdateLastUsedDate(spaceId string, key Key, timeStamp int64)
|
||||
}
|
||||
|
||||
func New() ObjectUsageUpdater {
|
||||
return &updater{}
|
||||
}
|
||||
|
||||
type updater struct {
|
||||
store objectstore.ObjectStore
|
||||
spaceService space.Service
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
msgBatch *mb.MB[message]
|
||||
}
|
||||
|
||||
func (u *updater) Name() string {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (u *updater) Init(a *app.App) error {
|
||||
u.store = app.MustComponent[objectstore.ObjectStore](a)
|
||||
u.spaceService = app.MustComponent[space.Service](a)
|
||||
u.msgBatch = mb.New[message](0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) Run(context.Context) error {
|
||||
u.ctx, u.cancel = context.WithCancel(context.Background())
|
||||
go u.lastUsedUpdateHandler()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) Close(context.Context) error {
|
||||
if u.cancel != nil {
|
||||
u.cancel()
|
||||
}
|
||||
if err := u.msgBatch.Close(); err != nil {
|
||||
log.Error("failed to close message batch", zap.Error(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) UpdateLastUsedDate(spaceId string, key Key, ts int64) {
|
||||
if err := u.msgBatch.Add(u.ctx, message{spaceId: spaceId, key: key, time: ts}); err != nil {
|
||||
log.Error("failed to add last used date info to message batch", zap.Error(err), zap.String("key", key.String()))
|
||||
}
|
||||
}
|
||||
|
||||
func (u *updater) lastUsedUpdateHandler() {
|
||||
var (
|
||||
accumulator = make(map[string]map[Key]int64)
|
||||
lock sync.Mutex
|
||||
)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-u.ctx.Done():
|
||||
return
|
||||
case <-time.After(updateInterval):
|
||||
lock.Lock()
|
||||
if len(accumulator) == 0 {
|
||||
lock.Unlock()
|
||||
continue
|
||||
}
|
||||
for spaceId, keys := range accumulator {
|
||||
log.Debug("updating lastUsedDate for objects in space", zap.Int("objects num", len(keys)), zap.String("spaceId", spaceId))
|
||||
u.updateLastUsedDateForKeysInSpace(spaceId, keys)
|
||||
}
|
||||
accumulator = make(map[string]map[Key]int64)
|
||||
lock.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
msgs, err := u.msgBatch.Wait(u.ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lock.Lock()
|
||||
for _, msg := range msgs {
|
||||
if keys := accumulator[msg.spaceId]; keys != nil {
|
||||
keys[msg.key] = msg.time
|
||||
} else {
|
||||
keys = map[Key]int64{
|
||||
msg.key: msg.time,
|
||||
}
|
||||
accumulator[msg.spaceId] = keys
|
||||
}
|
||||
}
|
||||
lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (u *updater) updateLastUsedDateForKeysInSpace(spaceId string, keys map[Key]int64) {
|
||||
spc, err := u.spaceService.Get(u.ctx, spaceId)
|
||||
if err != nil {
|
||||
log.Error("failed to get space", zap.String("spaceId", spaceId), zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
for key, timeStamp := range keys {
|
||||
if err = u.updateLastUsedDate(spc, key, timeStamp); err != nil {
|
||||
log.Error("failed to update last used date", zap.String("spaceId", spaceId), zap.String("key", key.String()), zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *updater) updateLastUsedDate(spc clientspace.Space, key Key, ts int64) error {
|
||||
uk, err := domain.UnmarshalUniqueKey(key.URL())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshall key: %w", err)
|
||||
}
|
||||
|
||||
if uk.SmartblockType() != coresb.SmartBlockTypeObjectType && uk.SmartblockType() != coresb.SmartBlockTypeRelation {
|
||||
return fmt.Errorf("cannot update lastUsedDate for object with invalid smartBlock type. Only object types and relations are expected")
|
||||
}
|
||||
|
||||
details, err := u.store.GetObjectByUniqueKey(spc.Id(), uk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get details: %w", err)
|
||||
}
|
||||
|
||||
id := pbtypes.GetString(details.Details, bundle.RelationKeyId.String())
|
||||
if id == "" {
|
||||
return fmt.Errorf("failed to get id from details: %w", err)
|
||||
}
|
||||
|
||||
if err = spc.DoCtx(u.ctx, id, func(sb smartblock.SmartBlock) error {
|
||||
st := sb.NewState()
|
||||
st.SetLocalDetail(bundle.RelationKeyLastUsedDate.String(), pbtypes.Int64(ts))
|
||||
return sb.Apply(st)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to set lastUsedDate to object: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetLastUsedDateForInitialObjectType(id string, details *types.Struct) {
|
||||
if !strings.HasPrefix(id, addr.BundledObjectTypeURLPrefix) || details == nil || details.Fields == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var priority int64
|
||||
switch id {
|
||||
case bundle.TypeKeyNote.BundledURL():
|
||||
priority = 1
|
||||
case bundle.TypeKeyPage.BundledURL():
|
||||
priority = 2
|
||||
case bundle.TypeKeyTask.BundledURL():
|
||||
priority = 3
|
||||
case bundle.TypeKeySet.BundledURL():
|
||||
priority = 4
|
||||
case bundle.TypeKeyCollection.BundledURL():
|
||||
priority = 5
|
||||
default:
|
||||
priority = 7
|
||||
}
|
||||
|
||||
// we do this trick to order crucial Anytype object types by last date
|
||||
lastUsed := time.Now().Add(time.Duration(-1 * priority * int64(maxInstallationTime))).Unix()
|
||||
details.Fields[bundle.RelationKeyLastUsedDate.String()] = pbtypes.Int64(lastUsed)
|
||||
}
|
139
core/block/editor/lastused/lastused_test.go
Normal file
139
core/block/editor/lastused/lastused_test.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
package lastused
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock/smarttest"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
"github.com/anyproto/anytype-heart/space/clientspace"
|
||||
"github.com/anyproto/anytype-heart/space/clientspace/mock_clientspace"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
func TestSetLastUsedDateForInitialType(t *testing.T) {
|
||||
isLastUsedDateGreater := func(details1, details2 *types.Struct) bool {
|
||||
return pbtypes.GetInt64(details1, bundle.RelationKeyLastUsedDate.String()) > pbtypes.GetInt64(details2, bundle.RelationKeyLastUsedDate.String())
|
||||
}
|
||||
|
||||
t.Run("object types are sorted by lastUsedDate in correct order", func(t *testing.T) {
|
||||
// given
|
||||
ots := []string{
|
||||
bundle.TypeKeySet.BundledURL(),
|
||||
bundle.TypeKeyNote.BundledURL(),
|
||||
bundle.TypeKeyCollection.BundledURL(),
|
||||
bundle.TypeKeyTask.BundledURL(),
|
||||
bundle.TypeKeyPage.BundledURL(),
|
||||
bundle.TypeKeyDiaryEntry.BundledURL(),
|
||||
bundle.TypeKeyAudio.BundledURL(),
|
||||
}
|
||||
rand.Shuffle(len(ots), func(i, j int) {
|
||||
ots[i], ots[j] = ots[j], ots[i]
|
||||
})
|
||||
detailMap := map[string]*types.Struct{}
|
||||
|
||||
// when
|
||||
for _, id := range ots {
|
||||
details := &types.Struct{Fields: make(map[string]*types.Value)}
|
||||
SetLastUsedDateForInitialObjectType(id, details)
|
||||
detailMap[id] = details
|
||||
}
|
||||
|
||||
// then
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeyNote.BundledURL()], detailMap[bundle.TypeKeyPage.BundledURL()]))
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeyPage.BundledURL()], detailMap[bundle.TypeKeyTask.BundledURL()]))
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeyTask.BundledURL()], detailMap[bundle.TypeKeySet.BundledURL()]))
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeySet.BundledURL()], detailMap[bundle.TypeKeyCollection.BundledURL()]))
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeyCollection.BundledURL()], detailMap[bundle.TypeKeyAudio.BundledURL()]))
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeyCollection.BundledURL()], detailMap[bundle.TypeKeyDiaryEntry.BundledURL()]))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateLastUsedDate(t *testing.T) {
|
||||
const spaceId = "space"
|
||||
|
||||
ts := time.Now().Unix()
|
||||
|
||||
isLastUsedDateRecent := func(details *types.Struct, deltaSeconds int64) bool {
|
||||
return pbtypes.GetInt64(details, bundle.RelationKeyLastUsedDate.String())+deltaSeconds > time.Now().Unix()
|
||||
}
|
||||
|
||||
store := objectstore.NewStoreFixture(t)
|
||||
store.AddObjects(t, []objectstore.TestObject{
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String(bundle.RelationKeyCamera.URL()),
|
||||
bundle.RelationKeySpaceId: pbtypes.String(spaceId),
|
||||
bundle.RelationKeyUniqueKey: pbtypes.String(bundle.RelationKeyCamera.URL()),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String(bundle.TypeKeyDiaryEntry.URL()),
|
||||
bundle.RelationKeySpaceId: pbtypes.String(spaceId),
|
||||
bundle.RelationKeyUniqueKey: pbtypes.String(bundle.TypeKeyDiaryEntry.URL()),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String("rel-custom"),
|
||||
bundle.RelationKeySpaceId: pbtypes.String(spaceId),
|
||||
bundle.RelationKeyUniqueKey: pbtypes.String("rel-custom"),
|
||||
},
|
||||
{
|
||||
bundle.RelationKeyId: pbtypes.String("opt-done"),
|
||||
bundle.RelationKeySpaceId: pbtypes.String(spaceId),
|
||||
bundle.RelationKeyUniqueKey: pbtypes.String("opt-done"),
|
||||
},
|
||||
})
|
||||
|
||||
u := updater{store: store}
|
||||
|
||||
getSpace := func() clientspace.Space {
|
||||
spc := mock_clientspace.NewMockSpace(t)
|
||||
spc.EXPECT().Id().Return(spaceId)
|
||||
spc.EXPECT().DoCtx(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(func(_ context.Context, id string, apply func(smartblock.SmartBlock) error) error {
|
||||
sb := smarttest.New(id)
|
||||
err := apply(sb)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, isLastUsedDateRecent(sb.LocalDetails(), 5))
|
||||
return nil
|
||||
})
|
||||
return spc
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
key Key
|
||||
getSpace func() clientspace.Space
|
||||
isErrorExpected bool
|
||||
}{
|
||||
{"built-in relation", bundle.RelationKeyCamera, getSpace, false},
|
||||
{"built-in type", bundle.TypeKeyDiaryEntry, getSpace, false},
|
||||
{"custom relation", domain.RelationKey("custom"), getSpace, false},
|
||||
{"option", domain.TypeKey("opt-done"), func() clientspace.Space {
|
||||
spc := mock_clientspace.NewMockSpace(t)
|
||||
return spc
|
||||
}, true},
|
||||
{"type that is not in store", bundle.TypeKeyAudio, func() clientspace.Space {
|
||||
spc := mock_clientspace.NewMockSpace(t)
|
||||
spc.EXPECT().Id().Return(spaceId)
|
||||
return spc
|
||||
}, true},
|
||||
} {
|
||||
t.Run("update lastUsedDate of "+tc.name, func(t *testing.T) {
|
||||
err := u.updateLastUsedDate(tc.getSpace(), tc.key, ts)
|
||||
if tc.isErrorExpected {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package objecttype
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/logging"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
const maxInstallationTime = 5 * time.Minute
|
||||
|
||||
var log = logging.Logger("update-last-used-date")
|
||||
|
||||
func UpdateLastUsedDate(spc smartblock.Space, store objectstore.ObjectStore, key domain.TypeKey) {
|
||||
uk, err := domain.UnmarshalUniqueKey(key.URL())
|
||||
if err != nil {
|
||||
log.Errorf("failed to unmarshall type key '%s': %w", key.String(), err)
|
||||
return
|
||||
}
|
||||
details, err := store.GetObjectByUniqueKey(spc.Id(), uk)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get details of type object '%s': %w", key.String(), err)
|
||||
return
|
||||
}
|
||||
id := pbtypes.GetString(details.Details, bundle.RelationKeyId.String())
|
||||
if id == "" {
|
||||
log.Errorf("failed to get id from details of type object '%s': %w", key.String(), err)
|
||||
return
|
||||
}
|
||||
if err = spc.Do(id, func(sb smartblock.SmartBlock) error {
|
||||
st := sb.NewState()
|
||||
st.SetLocalDetail(bundle.RelationKeyLastUsedDate.String(), pbtypes.Int64(time.Now().Unix()))
|
||||
return sb.Apply(st)
|
||||
}); err != nil {
|
||||
log.Errorf("failed to set lastUsedDate to type object '%s': %w", key.String(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func SetLastUsedDateForInitialObjectType(id string, details *types.Struct) {
|
||||
if !strings.HasPrefix(id, addr.BundledObjectTypeURLPrefix) || details == nil || details.Fields == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var priority int64
|
||||
switch id {
|
||||
case bundle.TypeKeyNote.BundledURL():
|
||||
priority = 1
|
||||
case bundle.TypeKeyPage.BundledURL():
|
||||
priority = 2
|
||||
case bundle.TypeKeyTask.BundledURL():
|
||||
priority = 3
|
||||
case bundle.TypeKeySet.BundledURL():
|
||||
priority = 4
|
||||
case bundle.TypeKeyCollection.BundledURL():
|
||||
priority = 5
|
||||
default:
|
||||
priority = 7
|
||||
}
|
||||
|
||||
// we do this trick to order crucial Anytype object types by last date
|
||||
lastUsed := time.Now().Add(time.Duration(-1 * priority * int64(maxInstallationTime))).Unix()
|
||||
details.Fields[bundle.RelationKeyLastUsedDate.String()] = pbtypes.Int64(lastUsed)
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package objecttype
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
func TestSetLastUsedDateForInitialType(t *testing.T) {
|
||||
isLastUsedDateGreater := func(details1, details2 *types.Struct) bool {
|
||||
return pbtypes.GetInt64(details1, bundle.RelationKeyLastUsedDate.String()) > pbtypes.GetInt64(details2, bundle.RelationKeyLastUsedDate.String())
|
||||
}
|
||||
|
||||
t.Run("object types are sorted by lastUsedDate in correct order", func(t *testing.T) {
|
||||
// given
|
||||
ots := []string{
|
||||
bundle.TypeKeySet.BundledURL(),
|
||||
bundle.TypeKeyNote.BundledURL(),
|
||||
bundle.TypeKeyCollection.BundledURL(),
|
||||
bundle.TypeKeyTask.BundledURL(),
|
||||
bundle.TypeKeyPage.BundledURL(),
|
||||
bundle.TypeKeyGoal.BundledURL(),
|
||||
bundle.TypeKeyAudio.BundledURL(),
|
||||
}
|
||||
rand.Shuffle(len(ots), func(i, j int) {
|
||||
ots[i], ots[j] = ots[j], ots[i]
|
||||
})
|
||||
detailMap := map[string]*types.Struct{}
|
||||
|
||||
// when
|
||||
for _, id := range ots {
|
||||
details := &types.Struct{Fields: make(map[string]*types.Value)}
|
||||
SetLastUsedDateForInitialObjectType(id, details)
|
||||
detailMap[id] = details
|
||||
}
|
||||
|
||||
// then
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeyNote.BundledURL()], detailMap[bundle.TypeKeyPage.BundledURL()]))
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeyPage.BundledURL()], detailMap[bundle.TypeKeyTask.BundledURL()]))
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeyTask.BundledURL()], detailMap[bundle.TypeKeySet.BundledURL()]))
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeySet.BundledURL()], detailMap[bundle.TypeKeyCollection.BundledURL()]))
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeyCollection.BundledURL()], detailMap[bundle.TypeKeyAudio.BundledURL()]))
|
||||
assert.True(t, isLastUsedDateGreater(detailMap[bundle.TypeKeyCollection.BundledURL()], detailMap[bundle.TypeKeyGoal.BundledURL()]))
|
||||
})
|
||||
}
|
|
@ -58,7 +58,7 @@ func (f *ObjectFactory) newPage(sb smartblock.SmartBlock) *Page {
|
|||
return &Page{
|
||||
SmartBlock: sb,
|
||||
ChangeReceiver: sb.(source.ChangeReceiver),
|
||||
AllOperations: basic.NewBasic(sb, f.objectStore, f.layoutConverter, f.fileObjectService),
|
||||
AllOperations: basic.NewBasic(sb, f.objectStore, f.layoutConverter, f.fileObjectService, f.lastUsedUpdater),
|
||||
IHistory: basic.NewHistory(sb),
|
||||
Text: stext.NewText(
|
||||
sb,
|
||||
|
|
|
@ -31,7 +31,7 @@ type participant struct {
|
|||
}
|
||||
|
||||
func (f *ObjectFactory) newParticipant(sb smartblock.SmartBlock) *participant {
|
||||
basicComponent := basic.NewBasic(sb, f.objectStore, f.layoutConverter, nil)
|
||||
basicComponent := basic.NewBasic(sb, f.objectStore, f.layoutConverter, nil, f.lastUsedUpdater)
|
||||
return &participant{
|
||||
SmartBlock: sb,
|
||||
DetailsUpdatable: basicComponent,
|
||||
|
|
|
@ -135,7 +135,7 @@ func newStoreFixture(t *testing.T) *objectstore.StoreFixture {
|
|||
func newParticipantTest(t *testing.T) (*participant, error) {
|
||||
sb := smarttest.New("root")
|
||||
store := newStoreFixture(t)
|
||||
basicComponent := basic.NewBasic(sb, store, nil, nil)
|
||||
basicComponent := basic.NewBasic(sb, store, nil, nil, nil)
|
||||
p := &participant{
|
||||
SmartBlock: sb,
|
||||
DetailsUpdatable: basicComponent,
|
||||
|
|
|
@ -40,7 +40,7 @@ func (f *ObjectFactory) newProfile(sb smartblock.SmartBlock) *Profile {
|
|||
fileComponent := file.NewFile(sb, f.fileBlockService, f.picker, f.processService, f.fileUploaderService)
|
||||
return &Profile{
|
||||
SmartBlock: sb,
|
||||
AllOperations: basic.NewBasic(sb, f.objectStore, f.layoutConverter, f.fileObjectService),
|
||||
AllOperations: basic.NewBasic(sb, f.objectStore, f.layoutConverter, f.fileObjectService, f.lastUsedUpdater),
|
||||
IHistory: basic.NewHistory(sb),
|
||||
Text: stext.NewText(
|
||||
sb,
|
||||
|
|
|
@ -37,6 +37,22 @@ func (sb *smartBlock) injectLinksDetails(s *state.State) {
|
|||
s.SetLocalDetail(bundle.RelationKeyLinks.String(), pbtypes.StringList(links))
|
||||
}
|
||||
|
||||
func (sb *smartBlock) injectMentions(s *state.State) {
|
||||
mentions := objectlink.DependentObjectIDs(s, sb.Space(), objectlink.Flags{
|
||||
Blocks: true,
|
||||
Details: false,
|
||||
Relations: false,
|
||||
Types: false,
|
||||
Collection: false,
|
||||
DataviewBlockOnlyTarget: true,
|
||||
NoSystemRelations: true,
|
||||
NoHiddenBundledRelations: true,
|
||||
NoImages: true,
|
||||
})
|
||||
mentions = slice.RemoveMut(mentions, sb.Id())
|
||||
s.SetDetailAndBundledRelation(bundle.RelationKeyMentions, pbtypes.StringList(mentions))
|
||||
}
|
||||
|
||||
func isBacklinksChanged(msgs []simple.EventMessage) bool {
|
||||
for _, msg := range msgs {
|
||||
if amend, ok := msg.Msg.Value.(*pb.EventMessageValueOfObjectDetailsAmend); ok {
|
||||
|
|
|
@ -1454,6 +1454,7 @@ func (sb *smartBlock) injectDerivedDetails(s *state.State, spaceID string, sbt s
|
|||
}
|
||||
|
||||
sb.injectLinksDetails(s)
|
||||
sb.injectMentions(s)
|
||||
sb.updateBackLinks(s)
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ func NewWidgetObject(
|
|||
objectStore objectstore.ObjectStore,
|
||||
layoutConverter converter.LayoutConverter,
|
||||
) *WidgetObject {
|
||||
bs := basic.NewBasic(sb, objectStore, layoutConverter, nil)
|
||||
bs := basic.NewBasic(sb, objectStore, layoutConverter, nil, nil)
|
||||
return &WidgetObject{
|
||||
SmartBlock: sb,
|
||||
Movable: bs,
|
||||
|
|
|
@ -35,7 +35,7 @@ type Workspaces struct {
|
|||
func (f *ObjectFactory) newWorkspace(sb smartblock.SmartBlock) *Workspaces {
|
||||
w := &Workspaces{
|
||||
SmartBlock: sb,
|
||||
AllOperations: basic.NewBasic(sb, f.objectStore, f.layoutConverter, f.fileObjectService),
|
||||
AllOperations: basic.NewBasic(sb, f.objectStore, f.layoutConverter, f.fileObjectService, f.lastUsedUpdater),
|
||||
IHistory: basic.NewHistory(sb),
|
||||
Text: stext.NewText(
|
||||
sb,
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/gogo/protobuf/types"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/lastused"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/block/restriction"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
|
@ -59,6 +60,7 @@ type service struct {
|
|||
bookmarkService bookmarkService
|
||||
spaceService space.Service
|
||||
templateService templateService
|
||||
lastUsedUpdater lastused.ObjectUsageUpdater
|
||||
}
|
||||
|
||||
func NewCreator() Service {
|
||||
|
@ -71,6 +73,7 @@ func (s *service) Init(a *app.App) (err error) {
|
|||
s.collectionService = app.MustComponent[collectionService](a)
|
||||
s.spaceService = app.MustComponent[space.Service](a)
|
||||
s.templateService = app.MustComponent[templateService](a)
|
||||
s.lastUsedUpdater = app.MustComponent[lastused.ObjectUsageUpdater](a)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/gogo/protobuf/types"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/objecttype"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/lastused"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
|
@ -226,7 +226,7 @@ func (s *service) prepareDetailsForInstallingObject(
|
|||
details.Fields[bundle.RelationKeyIsReadonly.String()] = pbtypes.Bool(false)
|
||||
|
||||
if isNewSpace {
|
||||
objecttype.SetLastUsedDateForInitialObjectType(sourceId, details)
|
||||
lastused.SetLastUsedDateForInitialObjectType(sourceId, details)
|
||||
}
|
||||
|
||||
bundledRelationIds := pbtypes.GetStringList(details, bundle.RelationKeyRecommendedRelations.String())
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/gogo/protobuf/types"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/objecttype"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/block/object/objectcache"
|
||||
|
@ -103,13 +102,7 @@ func (s *service) CreateSmartBlockFromStateInSpaceWithOptions(
|
|||
sb.Unlock()
|
||||
id = sb.Id()
|
||||
|
||||
if sbType == coresb.SmartBlockTypeObjectType && pbtypes.GetInt64(newDetails, bundle.RelationKeyLastUsedDate.String()) == 0 {
|
||||
objecttype.UpdateLastUsedDate(spc, s.objectStore, domain.TypeKey(
|
||||
strings.TrimPrefix(pbtypes.GetString(newDetails, bundle.RelationKeyUniqueKey.String()), addr.ObjectTypeKeyToIdPrefix)),
|
||||
)
|
||||
} else if pbtypes.GetInt64(newDetails, bundle.RelationKeyOrigin.String()) == int64(model.ObjectOrigin_none) {
|
||||
objecttype.UpdateLastUsedDate(spc, s.objectStore, objectTypeKeys[0])
|
||||
}
|
||||
s.updateLastUsedDate(spc.Id(), sbType, newDetails, objectTypeKeys[0])
|
||||
|
||||
ev.SmartblockCreateMs = time.Since(startTime).Milliseconds() - ev.SetDetailsMs - ev.WorkspaceCreateMs - ev.GetWorkspaceBlockWaitMs
|
||||
ev.SmartblockType = int(sbType)
|
||||
|
@ -118,6 +111,24 @@ func (s *service) CreateSmartBlockFromStateInSpaceWithOptions(
|
|||
return id, newDetails, nil
|
||||
}
|
||||
|
||||
func (s *service) updateLastUsedDate(spaceId string, sbType coresb.SmartBlockType, details *types.Struct, typeKey domain.TypeKey) {
|
||||
if pbtypes.GetInt64(details, bundle.RelationKeyLastUsedDate.String()) != 0 {
|
||||
return
|
||||
}
|
||||
uk := pbtypes.GetString(details, bundle.RelationKeyUniqueKey.String())
|
||||
ts := time.Now().Unix()
|
||||
switch sbType {
|
||||
case coresb.SmartBlockTypeObjectType:
|
||||
s.lastUsedUpdater.UpdateLastUsedDate(spaceId, domain.TypeKey(strings.TrimPrefix(uk, addr.ObjectTypeKeyToIdPrefix)), ts)
|
||||
case coresb.SmartBlockTypeRelation:
|
||||
s.lastUsedUpdater.UpdateLastUsedDate(spaceId, domain.RelationKey(strings.TrimPrefix(uk, addr.RelationKeyToIdPrefix)), ts)
|
||||
default:
|
||||
if pbtypes.GetInt64(details, bundle.RelationKeyOrigin.String()) == int64(model.ObjectOrigin_none) {
|
||||
s.lastUsedUpdater.UpdateLastUsedDate(spaceId, typeKey, ts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func objectTypeKeysToSmartBlockType(typeKeys []domain.TypeKey) coresb.SmartBlockType {
|
||||
// TODO Add validation for types that user can't create
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ func (mw *Middleware) ObjectSetDetails(cctx context.Context, req *pb.RpcObjectSe
|
|||
}
|
||||
return m
|
||||
}
|
||||
err := getService[details.Service](mw).SetDetails(ctx, req.ContextId, req.GetDetails())
|
||||
err := getService[details.Service](mw).SetDetailsAndUpdateLastUsed(ctx, req.ContextId, req.GetDetails())
|
||||
if err != nil {
|
||||
return response(pb.RpcObjectSetDetailsResponseError_UNKNOWN_ERROR, err)
|
||||
}
|
||||
|
|
62
docs/ComplexFilters.md
Normal file
62
docs/ComplexFilters.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
## Complex search filters
|
||||
|
||||
The majority of search filters for ObjectSearch-requests have simple format `key-condition-value`:
|
||||
```json
|
||||
{
|
||||
"RelationKey": "type",
|
||||
"condition": 2, // is not equal
|
||||
"value": {
|
||||
"stringValue": "ot-page"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
However, this format does not allow to build complex filters, that involve multiple relation keys analysis.
|
||||
So we introduce complex filters that uses struct-values to build up more sophisticated requests.
|
||||
|
||||
**type** field defined in the struct stands for complex filters types.
|
||||
|
||||
### Two relation values comparison
|
||||
|
||||
**type** = **valueFromRelation** allows to filter out objects by comparing values of two different relations.
|
||||
First relation is set to root _RelationKey_ field, second - to _relationKey_ field of the struct value.
|
||||
|
||||
For example, if we want to get all objects that were added to space before last modification, we can build up following filter:
|
||||
```json
|
||||
{
|
||||
"RelationKey": "addedDate",
|
||||
"condition": 4, // less
|
||||
"value": {
|
||||
"structValue" : {
|
||||
"fields": {
|
||||
"type": {
|
||||
"stringValue": "valueFromRelation"
|
||||
},
|
||||
"relationKey": {
|
||||
"stringValue": "lastModifiedDate"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All objects that has similar name and description:
|
||||
```json
|
||||
{
|
||||
"RelationKey": "name",
|
||||
"condition": 1, // equals
|
||||
"value": {
|
||||
"structValue": {
|
||||
"fields": {
|
||||
"type": {
|
||||
"stringValue": "valueFromRelation"
|
||||
},
|
||||
"relationKey": {
|
||||
"stringValue": "description"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,6 +1,6 @@
|
|||
## Use Case archives validation tool
|
||||
|
||||
To use Use Case archives validation tool head to [cmd/usecasevalidator](../cmd/usecasegenerator), build the program
|
||||
To use Use Case archives validation tool head to [cmd/usecasevalidator](../cmd/usecasevalidator), build the program
|
||||
`go build`
|
||||
and run it using
|
||||
`./usecasevalidator -path <path_to_archive>`
|
||||
|
@ -90,4 +90,4 @@ To choose the desired action **action** field should be provided to the JSON obj
|
|||
}
|
||||
```
|
||||
|
||||
More examples could be found in **rules.json** file
|
||||
More examples could be found in **rules.json** file
|
||||
|
|
33
go.mod
33
go.mod
|
@ -7,7 +7,7 @@ require (
|
|||
github.com/PuerkitoBio/goquery v1.9.2
|
||||
github.com/VividCortex/ewma v1.2.0
|
||||
github.com/adrium/goheif v0.0.0-20230113233934-ca402e77a786
|
||||
github.com/anyproto/any-store v0.0.2-0.20240812125400-7f6f6d062ebf
|
||||
github.com/anyproto/any-store v0.0.4
|
||||
github.com/anyproto/any-sync v0.4.30
|
||||
github.com/anyproto/go-naturaldate/v2 v2.0.2-0.20230524105841-9829cfd13438
|
||||
github.com/anyproto/tantivy-go v0.1.0
|
||||
|
@ -76,7 +76,7 @@ require (
|
|||
github.com/otiai10/copy v1.14.0
|
||||
github.com/otiai10/opengraph/v2 v2.1.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.20.2
|
||||
github.com/prometheus/client_golang v1.20.3
|
||||
github.com/pseudomuto/protoc-gen-doc v1.5.1
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
github.com/samber/lo v1.47.0
|
||||
|
@ -93,13 +93,13 @@ require (
|
|||
go.uber.org/mock v0.4.0
|
||||
go.uber.org/multierr v1.11.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
golang.org/x/image v0.19.0
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
|
||||
golang.org/x/image v0.20.0
|
||||
golang.org/x/mobile v0.0.0-20240806205939-81131f6468ab
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/oauth2 v0.22.0
|
||||
golang.org/x/text v0.17.0
|
||||
google.golang.org/grpc v1.66.0
|
||||
golang.org/x/net v0.29.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/text v0.18.0
|
||||
google.golang.org/grpc v1.66.1
|
||||
gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20180125164251-1832d8546a9f
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
storj.io/drpc v0.0.34
|
||||
|
@ -154,7 +154,7 @@ require (
|
|||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c // indirect
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
|
||||
github.com/fogleman/gg v1.3.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
|
@ -174,7 +174,7 @@ require (
|
|||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/flatbuffers v1.12.1 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f // indirect
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||
|
@ -221,6 +221,7 @@ require (
|
|||
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mwitkow/go-proto-validators v0.3.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
|
||||
|
@ -232,6 +233,7 @@ require (
|
|||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/pseudomuto/protokit v0.2.1 // indirect
|
||||
github.com/quic-go/quic-go v0.46.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rs/cors v1.7.0 // indirect
|
||||
github.com/rs/zerolog v1.29.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
|
@ -258,11 +260,11 @@ require (
|
|||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect
|
||||
|
@ -271,7 +273,12 @@ require (
|
|||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
modernc.org/libc v1.60.1 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.32.0 // indirect
|
||||
nhooyr.io/websocket v1.8.7 // indirect
|
||||
zombiezen.com/go/sqlite v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/dgraph-io/badger/v4 => github.com/anyproto/badger/v4 v4.2.1-0.20240110160636-80743fa3d580
|
||||
|
|
81
go.sum
81
go.sum
|
@ -83,8 +83,8 @@ github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxB
|
|||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/anyproto/any-store v0.0.2-0.20240812125400-7f6f6d062ebf h1:9VTQc/011fVyucu9bICmjF6+y/T9QdZv6MN7X4gM4S0=
|
||||
github.com/anyproto/any-store v0.0.2-0.20240812125400-7f6f6d062ebf/go.mod h1:JV7dJ8+xF1VaCQgFQsD8SZSNlxxTGFbDiF8llkB2ILc=
|
||||
github.com/anyproto/any-store v0.0.4 h1:Iv5XXOi0WM0g0yPNYixFA/MQpAxwHpJ5VtNuRszvE3g=
|
||||
github.com/anyproto/any-store v0.0.4/go.mod h1:MA3sSqRXIp0h9bC2LnMuDGVEFGB3BkI/UuWNCleGHMs=
|
||||
github.com/anyproto/any-sync v0.4.30 h1:2nnoVQF9WiBIw8pzwuOubwqf7a+xvZPDkXHmykTPi0k=
|
||||
github.com/anyproto/any-sync v0.4.30/go.mod h1:YjwTdTRL6b6GuutJNboJJ1M5D/kkdeSf9qF5xP6wG7Y=
|
||||
github.com/anyproto/badger/v4 v4.2.1-0.20240110160636-80743fa3d580 h1:Ba80IlCCxkZ9H1GF+7vFu/TSpPvbpDCxXJ5ogc4euYc=
|
||||
|
@ -297,8 +297,9 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ
|
|||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
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/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
|
@ -489,8 +490,8 @@ 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-20240402174815-29b9bb013b0f h1:f00RU+zOX+B3rLAmMMkzHUF2h1z4DeYR9tTCvEq2REY=
|
||||
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
@ -1106,6 +1107,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
|
|||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
|
@ -1217,8 +1220,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
|
|||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
|
||||
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
|
@ -1257,6 +1260,8 @@ github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtn
|
|||
github.com/quic-go/webtransport-go v0.8.0 h1:HxSrwun11U+LlmwpgM1kEqIqH90IT4N8auv/cD7QFJg=
|
||||
github.com/quic-go/webtransport-go v0.8.0/go.mod h1:N99tjprW432Ut5ONql/aUhSLT0YVSlwHohQsuac9WaM=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
|
@ -1513,8 +1518,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
|
|||
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.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -1529,14 +1534,14 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||
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-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
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/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
|
||||
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
|
||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
||||
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
|
||||
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=
|
||||
|
@ -1628,8 +1633,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
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=
|
||||
|
@ -1639,8 +1644,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.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
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=
|
||||
|
@ -1749,8 +1754,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
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=
|
||||
|
@ -1758,8 +1763,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
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=
|
||||
|
@ -1771,8 +1776,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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=
|
||||
|
@ -1948,8 +1953,8 @@ google.golang.org/grpc v1.32.0/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.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
|
||||
google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
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=
|
||||
|
@ -2006,6 +2011,30 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
|||
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=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
|
||||
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
|
||||
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s=
|
||||
modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
|
||||
modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
|
@ -2017,3 +2046,5 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
|||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
storj.io/drpc v0.0.34 h1:q9zlQKfJ5A7x8NQNFk8x7eKUF78FMhmAbZLnFK+og7I=
|
||||
storj.io/drpc v0.0.34/go.mod h1:Y9LZaa8esL1PW2IDMqJE7CFSNq7d5bQ3RI7mGPtmKMg=
|
||||
zombiezen.com/go/sqlite v1.3.0 h1:98g1gnCm+CNz6AuQHu0gqyw7gR2WU3O3PJufDOStpUs=
|
||||
zombiezen.com/go/sqlite v1.3.0/go.mod h1:yRl27//s/9aXU3RWs8uFQwjkTG9gYNGEls6+6SvrclY=
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
const RelationChecksum = "031657c702d56fbad494e48152066b3e7e659d9d95dc67e5ef31378f2c0fd81e"
|
||||
const RelationChecksum = "68778695c5d49dd1ffbab91e13bd9769d80fd89de3a77902cec1c5066e5495a8"
|
||||
const (
|
||||
RelationKeyTag domain.RelationKey = "tag"
|
||||
RelationKeyCamera domain.RelationKey = "camera"
|
||||
|
@ -141,6 +141,7 @@ const (
|
|||
RelationKeySyncStatus domain.RelationKey = "syncStatus"
|
||||
RelationKeySyncDate domain.RelationKey = "syncDate"
|
||||
RelationKeySyncError domain.RelationKey = "syncError"
|
||||
RelationKeyMentions domain.RelationKey = "mentions"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -1082,6 +1083,19 @@ var (
|
|||
ReadOnlyRelation: true,
|
||||
Scope: model.Relation_type,
|
||||
},
|
||||
RelationKeyMentions: {
|
||||
|
||||
DataSource: model.Relation_local,
|
||||
Description: "Objects that are mentioned in blocks of this object",
|
||||
Format: model.RelationFormat_object,
|
||||
Hidden: true,
|
||||
Id: "_brmentions",
|
||||
Key: "mentions",
|
||||
Name: "Mentions",
|
||||
ReadOnly: true,
|
||||
ReadOnlyRelation: true,
|
||||
Scope: model.Relation_type,
|
||||
},
|
||||
RelationKeyMood: {
|
||||
|
||||
DataSource: model.Relation_details,
|
||||
|
|
|
@ -1328,5 +1328,15 @@
|
|||
"name": "Sync error",
|
||||
"readonly": true,
|
||||
"source": "local"
|
||||
},
|
||||
{
|
||||
"description": "Objects that are mentioned in blocks of this object",
|
||||
"format": "object",
|
||||
"hidden": true,
|
||||
"key": "mentions",
|
||||
"maxCount": 0,
|
||||
"name": "Mentions",
|
||||
"readonly": true,
|
||||
"source": "local"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -6,7 +6,7 @@ package bundle
|
|||
|
||||
import domain "github.com/anyproto/anytype-heart/core/domain"
|
||||
|
||||
const SystemRelationsChecksum = "71704f9c0a69b84194e54b4db1d4709f863b2eb7cb56e7635afd73d212c8bf0e"
|
||||
const SystemRelationsChecksum = "86abb054e3ab3db5feb72fee757ed9e3aaa012e3137c5f99e1baba8cfcf3c3f2"
|
||||
|
||||
// SystemRelations contains relations that have some special biz logic depends on them in some objects
|
||||
// in case EVERY object depend on the relation please add it to RequiredInternalRelations
|
||||
|
@ -79,4 +79,5 @@ var SystemRelations = append(RequiredInternalRelations, []domain.RelationKey{
|
|||
RelationKeyGlobalName,
|
||||
RelationKeyScope,
|
||||
RelationKeyLastUsedDate,
|
||||
RelationKeyMentions,
|
||||
}...)
|
||||
|
|
|
@ -87,5 +87,6 @@
|
|||
"syncStatus",
|
||||
"syncError",
|
||||
"scope",
|
||||
"lastUsedDate"
|
||||
"lastUsedDate",
|
||||
"mentions"
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
const TypeChecksum = "745e40062786ba9e43273b0009612a02af1eb9920ecaf6fab191f86b5cdd33f3"
|
||||
const TypeChecksum = "8b79add6b322acbcee123ad178c1aee523a2d9b9f219c391903530b29e554a1e"
|
||||
const (
|
||||
TypePrefix = "_ot"
|
||||
)
|
||||
|
@ -54,6 +54,7 @@ var (
|
|||
Readonly: true,
|
||||
RelationLinks: []*model.RelationLink{MustGetRelationLink(RelationKeyArtist), MustGetRelationLink(RelationKeyAudioAlbum), MustGetRelationLink(RelationKeyAudioAlbumTrackNumber), MustGetRelationLink(RelationKeyAudioGenre), MustGetRelationLink(RelationKeyAudioLyrics), MustGetRelationLink(RelationKeyReleasedYear), MustGetRelationLink(RelationKeySizeInBytes), MustGetRelationLink(RelationKeyFileMimeType), MustGetRelationLink(RelationKeyAddedDate), MustGetRelationLink(RelationKeyFileExt), MustGetRelationLink(RelationKeyOrigin)},
|
||||
RestrictObjectCreation: true,
|
||||
Revision: 1,
|
||||
Types: []model.SmartBlockType{model.SmartBlockType_File},
|
||||
Url: TypePrefix + "audio",
|
||||
},
|
||||
|
@ -144,6 +145,7 @@ var (
|
|||
Readonly: true,
|
||||
RelationLinks: []*model.RelationLink{MustGetRelationLink(RelationKeyFileMimeType), MustGetRelationLink(RelationKeySizeInBytes), MustGetRelationLink(RelationKeyAddedDate), MustGetRelationLink(RelationKeyFileExt), MustGetRelationLink(RelationKeyOrigin)},
|
||||
RestrictObjectCreation: true,
|
||||
Revision: 1,
|
||||
Types: []model.SmartBlockType{model.SmartBlockType_File},
|
||||
Url: TypePrefix + "file",
|
||||
},
|
||||
|
@ -167,6 +169,7 @@ var (
|
|||
Readonly: true,
|
||||
RelationLinks: []*model.RelationLink{MustGetRelationLink(RelationKeyFileMimeType), MustGetRelationLink(RelationKeyWidthInPixels), MustGetRelationLink(RelationKeyCamera), MustGetRelationLink(RelationKeyHeightInPixels), MustGetRelationLink(RelationKeySizeInBytes), MustGetRelationLink(RelationKeyCameraIso), MustGetRelationLink(RelationKeyAperture), MustGetRelationLink(RelationKeyExposure), MustGetRelationLink(RelationKeyAddedDate), MustGetRelationLink(RelationKeyFocalRatio), MustGetRelationLink(RelationKeyFileExt), MustGetRelationLink(RelationKeyOrigin)},
|
||||
RestrictObjectCreation: true,
|
||||
Revision: 1,
|
||||
Types: []model.SmartBlockType{model.SmartBlockType_File},
|
||||
Url: TypePrefix + "image",
|
||||
},
|
||||
|
@ -351,6 +354,7 @@ var (
|
|||
Readonly: true,
|
||||
RelationLinks: []*model.RelationLink{MustGetRelationLink(RelationKeySizeInBytes), MustGetRelationLink(RelationKeyFileMimeType), MustGetRelationLink(RelationKeyCamera), MustGetRelationLink(RelationKeyHeightInPixels), MustGetRelationLink(RelationKeyWidthInPixels), MustGetRelationLink(RelationKeyCameraIso), MustGetRelationLink(RelationKeyAperture), MustGetRelationLink(RelationKeyExposure), MustGetRelationLink(RelationKeyAddedDate), MustGetRelationLink(RelationKeyFileExt), MustGetRelationLink(RelationKeyOrigin)},
|
||||
RestrictObjectCreation: true,
|
||||
Revision: 1,
|
||||
Types: []model.SmartBlockType{model.SmartBlockType_File},
|
||||
Url: TypePrefix + "video",
|
||||
},
|
||||
|
|
|
@ -156,7 +156,8 @@
|
|||
"origin"
|
||||
],
|
||||
"description": "The recording of moving visual images",
|
||||
"restrictObjectCreation": true
|
||||
"restrictObjectCreation": true,
|
||||
"revision": 1
|
||||
},
|
||||
{
|
||||
"id": "dashboard",
|
||||
|
@ -358,7 +359,8 @@
|
|||
"origin"
|
||||
],
|
||||
"description": "A representation of the external form of a person or thing in art",
|
||||
"restrictObjectCreation": true
|
||||
"restrictObjectCreation": true,
|
||||
"revision": 1
|
||||
},
|
||||
{
|
||||
"id": "profile",
|
||||
|
@ -398,7 +400,8 @@
|
|||
"origin"
|
||||
],
|
||||
"description": "Sound when recorded, with ability to reproduce",
|
||||
"restrictObjectCreation": true
|
||||
"restrictObjectCreation": true,
|
||||
"revision": 1
|
||||
},
|
||||
{
|
||||
"id": "goal",
|
||||
|
@ -435,7 +438,8 @@
|
|||
"origin"
|
||||
],
|
||||
"description": "Computer resource for recording data in a computer storage device",
|
||||
"restrictObjectCreation": true
|
||||
"restrictObjectCreation": true,
|
||||
"revision": 1
|
||||
},
|
||||
{
|
||||
"id": "project",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
@ -116,6 +117,15 @@ func makeFilterByCondition(spaceID string, rawFilter *model.BlockContentDataview
|
|||
Value: pbtypes.Bool(true),
|
||||
}
|
||||
}
|
||||
|
||||
if str := rawFilter.Value.GetStructValue(); str != nil {
|
||||
filter, err := makeComplexFilter(rawFilter, str)
|
||||
if err == nil {
|
||||
return filter, nil
|
||||
}
|
||||
log.Errorf("failed to build complex filter: %v", err)
|
||||
}
|
||||
|
||||
switch rawFilter.Condition {
|
||||
case model.BlockContentDataviewFilter_Equal,
|
||||
model.BlockContentDataviewFilter_Greater,
|
||||
|
@ -203,6 +213,19 @@ func makeFilterByCondition(spaceID string, rawFilter *model.BlockContentDataview
|
|||
}
|
||||
}
|
||||
|
||||
func makeComplexFilter(rawFilter *model.BlockContentDataviewFilter, s *types.Struct) (Filter, error) {
|
||||
filterType := pbtypes.GetString(s, bundle.RelationKeyType.String())
|
||||
// TODO: rewrite to switch statement once we have more filter types
|
||||
if filterType == "valueFromRelation" {
|
||||
return Filter2ValuesComp{
|
||||
Key1: rawFilter.RelationKey,
|
||||
Key2: pbtypes.GetString(s, bundle.RelationKeyRelationKey.String()),
|
||||
Cond: rawFilter.Condition,
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported type of complex filter: %s", filterType)
|
||||
}
|
||||
|
||||
type WithNestedFilter interface {
|
||||
IterateNestedFilters(func(nestedFilter Filter) error) error
|
||||
}
|
||||
|
@ -845,3 +868,93 @@ func (i *FilterNestedNotIn) AnystoreFilter() query.Filter {
|
|||
func (i *FilterNestedNotIn) IterateNestedFilters(fn func(nestedFilter Filter) error) error {
|
||||
return fn(i)
|
||||
}
|
||||
|
||||
type Filter2ValuesComp struct {
|
||||
Key1, Key2 string
|
||||
Cond model.BlockContentDataviewFilterCondition
|
||||
}
|
||||
|
||||
func (i Filter2ValuesComp) FilterObject(g *types.Struct) bool {
|
||||
val1 := pbtypes.Get(g, i.Key1)
|
||||
val2 := pbtypes.Get(g, i.Key2)
|
||||
eq := FilterEq{Value: val2, Cond: i.Cond}
|
||||
return eq.filterObject(val1)
|
||||
}
|
||||
|
||||
func (i Filter2ValuesComp) AnystoreFilter() query.Filter {
|
||||
var op query.CompOp
|
||||
switch i.Cond {
|
||||
case model.BlockContentDataviewFilter_Equal:
|
||||
op = query.CompOpEq
|
||||
case model.BlockContentDataviewFilter_Greater:
|
||||
op = query.CompOpGt
|
||||
case model.BlockContentDataviewFilter_GreaterOrEqual:
|
||||
op = query.CompOpGte
|
||||
case model.BlockContentDataviewFilter_Less:
|
||||
op = query.CompOpLt
|
||||
case model.BlockContentDataviewFilter_LessOrEqual:
|
||||
op = query.CompOpLte
|
||||
case model.BlockContentDataviewFilter_NotEqual:
|
||||
op = query.CompOpNe
|
||||
}
|
||||
return &Anystore2ValuesComp{
|
||||
RelationKey1: i.Key1,
|
||||
RelationKey2: i.Key2,
|
||||
CompOp: op,
|
||||
}
|
||||
}
|
||||
|
||||
type Anystore2ValuesComp struct {
|
||||
RelationKey1, RelationKey2 string
|
||||
CompOp query.CompOp
|
||||
buf1, buf2 []byte
|
||||
}
|
||||
|
||||
func (e *Anystore2ValuesComp) Ok(v *fastjson.Value) bool {
|
||||
value1 := v.Get(e.RelationKey1)
|
||||
value2 := v.Get(e.RelationKey2)
|
||||
e.buf1 = encoding.AppendJSONValue(e.buf1[:0], value1)
|
||||
e.buf2 = encoding.AppendJSONValue(e.buf2[:0], value2)
|
||||
comp := bytes.Compare(e.buf1, e.buf2)
|
||||
switch e.CompOp {
|
||||
case query.CompOpEq:
|
||||
return comp == 0
|
||||
case query.CompOpGt:
|
||||
return comp > 0
|
||||
case query.CompOpGte:
|
||||
return comp >= 0
|
||||
case query.CompOpLt:
|
||||
return comp < 0
|
||||
case query.CompOpLte:
|
||||
return comp <= 0
|
||||
case query.CompOpNe:
|
||||
return comp != 0
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected comp op: %v", e.CompOp))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Anystore2ValuesComp) IndexBounds(_ string, bs query.Bounds) (bounds query.Bounds) {
|
||||
return bs
|
||||
}
|
||||
|
||||
func (e *Anystore2ValuesComp) String() string {
|
||||
var comp string
|
||||
switch e.CompOp {
|
||||
case query.CompOpEq:
|
||||
comp = "$eq"
|
||||
case query.CompOpGt:
|
||||
comp = "$gt"
|
||||
case query.CompOpGte:
|
||||
comp = "$gte"
|
||||
case query.CompOpLt:
|
||||
comp = "$lt"
|
||||
case query.CompOpLte:
|
||||
comp = "$lte"
|
||||
case query.CompOpNe:
|
||||
comp = "$ne"
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected comp op: %v", e.CompOp))
|
||||
}
|
||||
return fmt.Sprintf(`{"$comp_values": {"%s": ["%s", "%s"]}}`, comp, e.RelationKey1, e.RelationKey2)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-store/query"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
@ -19,11 +18,10 @@ import (
|
|||
func assertFilter(t *testing.T, f Filter, obj *types.Struct, expected bool) {
|
||||
assert.Equal(t, expected, f.FilterObject(obj))
|
||||
anystoreFilter := f.AnystoreFilter()
|
||||
_, err := query.ParseCondition(anystoreFilter.String())
|
||||
require.NoError(t, err, anystoreFilter.String())
|
||||
arena := &fastjson.Arena{}
|
||||
val := pbtypes.ProtoToJson(arena, obj)
|
||||
assert.Equal(t, expected, anystoreFilter.Ok(val))
|
||||
result := anystoreFilter.Ok(val)
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
|
||||
func TestEq_FilterObject(t *testing.T) {
|
||||
|
@ -886,3 +884,50 @@ func TestMakeFilters(t *testing.T) {
|
|||
assert.NotNil(t, filters.(FiltersOr)[1].(FilterEq))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilter2ValuesComp_FilterObject(t *testing.T) {
|
||||
t.Run("equal", func(t *testing.T) {
|
||||
eq := Filter2ValuesComp{
|
||||
Key1: "a",
|
||||
Key2: "b",
|
||||
Cond: model.BlockContentDataviewFilter_Equal,
|
||||
}
|
||||
obj1 := &types.Struct{Fields: map[string]*types.Value{
|
||||
"a": pbtypes.String("x"),
|
||||
"b": pbtypes.String("x"),
|
||||
}}
|
||||
obj2 := &types.Struct{Fields: map[string]*types.Value{
|
||||
"a": pbtypes.String("x"),
|
||||
"b": pbtypes.String("y"),
|
||||
}}
|
||||
obj3 := &types.Struct{Fields: map[string]*types.Value{
|
||||
"b": pbtypes.String("x"),
|
||||
}}
|
||||
assertFilter(t, eq, obj1, true)
|
||||
assertFilter(t, eq, obj2, false)
|
||||
assertFilter(t, eq, obj3, false)
|
||||
})
|
||||
|
||||
t.Run("greater", func(t *testing.T) {
|
||||
eq := Filter2ValuesComp{
|
||||
Key1: "a",
|
||||
Key2: "b",
|
||||
Cond: model.BlockContentDataviewFilter_Greater,
|
||||
}
|
||||
obj1 := &types.Struct{Fields: map[string]*types.Value{
|
||||
"a": pbtypes.Int64(100),
|
||||
"b": pbtypes.Int64(200),
|
||||
}}
|
||||
obj2 := &types.Struct{Fields: map[string]*types.Value{
|
||||
"a": pbtypes.Int64(300),
|
||||
"b": pbtypes.Int64(-500),
|
||||
}}
|
||||
obj3 := &types.Struct{Fields: map[string]*types.Value{
|
||||
"a": pbtypes.String("xxx"),
|
||||
"b": pbtypes.String("ddd"),
|
||||
}}
|
||||
assertFilter(t, eq, obj1, false)
|
||||
assertFilter(t, eq, obj2, true)
|
||||
assertFilter(t, eq, obj3, true)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -246,6 +246,14 @@ func (s *dsObjectStore) runDatabase(ctx context.Context, path string) error {
|
|||
s.anyStore = store
|
||||
|
||||
objectIndexes := []anystore.IndexInfo{
|
||||
{
|
||||
Name: "uniqueKey",
|
||||
Fields: []string{bundle.RelationKeyUniqueKey.String()},
|
||||
},
|
||||
{
|
||||
Name: "source",
|
||||
Fields: []string{bundle.RelationKeySource.String()},
|
||||
},
|
||||
{
|
||||
Name: "layout",
|
||||
Fields: []string{bundle.RelationKeyLayout.String()},
|
||||
|
@ -254,33 +262,23 @@ func (s *dsObjectStore) runDatabase(ctx context.Context, path string) error {
|
|||
Name: "type",
|
||||
Fields: []string{bundle.RelationKeyType.String()},
|
||||
},
|
||||
{
|
||||
Name: "spaceId",
|
||||
Fields: []string{bundle.RelationKeySpaceId.String()},
|
||||
},
|
||||
{
|
||||
Name: "relationKey",
|
||||
Fields: []string{bundle.RelationKeyRelationKey.String()},
|
||||
},
|
||||
{
|
||||
Name: "uniqueKey",
|
||||
Fields: []string{bundle.RelationKeyUniqueKey.String()},
|
||||
},
|
||||
{
|
||||
Name: "lastModifiedDate",
|
||||
Fields: []string{bundle.RelationKeyLastModifiedDate.String()},
|
||||
},
|
||||
{
|
||||
Name: "syncStatus",
|
||||
Fields: []string{bundle.RelationKeySyncStatus.String()},
|
||||
Name: "fileId",
|
||||
Fields: []string{bundle.RelationKeyFileId.String()},
|
||||
Sparse: true,
|
||||
},
|
||||
{
|
||||
Name: "isDeleted",
|
||||
Fields: []string{bundle.RelationKeyIsDeleted.String()},
|
||||
},
|
||||
{
|
||||
Name: "isArchived",
|
||||
Fields: []string{bundle.RelationKeyIsArchived.String()},
|
||||
Name: "oldAnytypeID",
|
||||
Fields: []string{bundle.RelationKeyOldAnytypeID.String()},
|
||||
Sparse: true,
|
||||
},
|
||||
}
|
||||
err = s.addIndexes(ctx, objects, objectIndexes)
|
||||
|
@ -315,6 +313,7 @@ func (s *dsObjectStore) runDatabase(ctx context.Context, path string) error {
|
|||
func (s *dsObjectStore) addIndexes(ctx context.Context, coll anystore.Collection, indexes []anystore.IndexInfo) error {
|
||||
gotIndexes := coll.GetIndexes()
|
||||
toCreate := indexes[:0]
|
||||
var toDrop []string
|
||||
for _, idx := range indexes {
|
||||
if !slices.ContainsFunc(gotIndexes, func(i anystore.Index) bool {
|
||||
return i.Info().Name == idx.Name
|
||||
|
@ -322,14 +321,33 @@ func (s *dsObjectStore) addIndexes(ctx context.Context, coll anystore.Collection
|
|||
toCreate = append(toCreate, idx)
|
||||
}
|
||||
}
|
||||
for _, idx := range gotIndexes {
|
||||
if !slices.ContainsFunc(indexes, func(i anystore.IndexInfo) bool {
|
||||
return i.Name == idx.Info().Name
|
||||
}) {
|
||||
toDrop = append(toDrop, idx.Info().Name)
|
||||
}
|
||||
}
|
||||
if len(toDrop) > 0 {
|
||||
for _, indexName := range toDrop {
|
||||
if err := coll.DropIndex(ctx, indexName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return coll.EnsureIndex(ctx, toCreate...)
|
||||
}
|
||||
|
||||
func (s *dsObjectStore) WriteTx(ctx context.Context) (anystore.WriteTx, error) {
|
||||
return s.anyStore.WriteTx(ctx)
|
||||
}
|
||||
|
||||
func (s *dsObjectStore) Close(_ context.Context) (err error) {
|
||||
s.componentCtxCancel()
|
||||
if s.objects != nil {
|
||||
err = errors.Join(err, s.objects.Close())
|
||||
}
|
||||
// TODO Close collections
|
||||
if s.anyStore != nil {
|
||||
err = errors.Join(err, s.anyStore.Close())
|
||||
}
|
||||
|
|
|
@ -4,9 +4,11 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
)
|
||||
|
||||
type SpaceWithCtx interface {
|
||||
DoCtx(ctx context.Context, objectId string, apply func(sb smartblock.SmartBlock) error) error
|
||||
Id() string
|
||||
DeriveObjectID(ctx context.Context, uniqueKey domain.UniqueKey) (id string, err error)
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/samber/lo"
|
||||
"go.uber.org/zap"
|
||||
|
@ -20,6 +22,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/dependencies"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
"github.com/anyproto/anytype-heart/util/slice"
|
||||
)
|
||||
|
||||
type detailsSettable interface {
|
||||
|
@ -100,6 +103,16 @@ func reviseSystemObject(ctx context.Context, log logger.CtxLogger, space depende
|
|||
return false, nil
|
||||
}
|
||||
details := buildDiffDetails(marketObject, localObject)
|
||||
|
||||
recRelsDetail, err := checkRecommendedRelations(ctx, space, marketObject, localObject)
|
||||
if err != nil {
|
||||
log.Error("failed to check recommended relations", zap.Error(err))
|
||||
}
|
||||
|
||||
if recRelsDetail != nil {
|
||||
details = append(details, recRelsDetail)
|
||||
}
|
||||
|
||||
if len(details) != 0 {
|
||||
log.Debug("updating system object", zap.String("source", source), zap.String("space", space.Id()))
|
||||
if err := space.DoCtx(ctx, pbtypes.GetString(localObject, bundle.RelationKeyId.String()), func(sb smartblock.SmartBlock) error {
|
||||
|
@ -150,3 +163,44 @@ func buildDiffDetails(origin, current *types.Struct) (details []*model.Detail) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkRecommendedRelations(ctx context.Context, space dependencies.SpaceWithCtx, origin, current *types.Struct) (newValue *model.Detail, err error) {
|
||||
localIds := pbtypes.GetStringList(current, bundle.RelationKeyRecommendedRelations.String())
|
||||
bundledIds := pbtypes.GetStringList(origin, bundle.RelationKeyRecommendedRelations.String())
|
||||
|
||||
newIds := make([]string, 0, len(bundledIds))
|
||||
for _, bundledId := range bundledIds {
|
||||
if !strings.HasPrefix(bundledId, addr.BundledRelationURLPrefix) {
|
||||
return nil, fmt.Errorf("invalid recommended bundled relation id: %s. %s prefix is expected",
|
||||
bundledId, addr.BundledRelationURLPrefix)
|
||||
}
|
||||
key := strings.TrimPrefix(bundledId, addr.BundledRelationURLPrefix)
|
||||
uk, err := domain.NewUniqueKey(coresb.SmartBlockTypeRelation, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// we should add only system relations to object types, because non-system could be not installed to space yet
|
||||
if !lo.Contains(bundle.SystemRelations, domain.RelationKey(uk.InternalKey())) {
|
||||
log.Debug("recommended relation is not system, so we are not adding it to the type object", zap.String("relation key", key))
|
||||
continue
|
||||
}
|
||||
|
||||
id, err := space.DeriveObjectID(ctx, uk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive recommended relation with key '%s': %w", key, err)
|
||||
}
|
||||
|
||||
newIds = append(newIds, id)
|
||||
}
|
||||
|
||||
_, added := slice.DifferenceRemovedAdded(localIds, newIds)
|
||||
if len(added) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &model.Detail{
|
||||
Key: bundle.RelationKeyRecommendedRelations.String(),
|
||||
Value: pbtypes.StringList(append(localIds, added...)),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
|
||||
mock_space "github.com/anyproto/anytype-heart/space/clientspace/mock_clientspace"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
@ -217,4 +219,72 @@ func TestReviseSystemObject(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.True(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("recommendedRelations list is updated", func(t *testing.T) {
|
||||
// given
|
||||
rel := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyRevision.String(): pbtypes.Int64(1),
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_otpage"),
|
||||
bundle.RelationKeyUniqueKey.String(): pbtypes.String("ot-page"),
|
||||
bundle.RelationKeyRecommendedRelations.String(): pbtypes.StringList([]string{"rel-name"}),
|
||||
}}
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().DoCtx(mock.Anything, mock.Anything, mock.Anything).Times(1).Return(nil)
|
||||
space.EXPECT().Id().Times(1).Return("")
|
||||
space.EXPECT().DeriveObjectID(mock.Anything, mock.Anything).RunAndReturn(func(_ context.Context, key domain.UniqueKey) (string, error) {
|
||||
return addr.ObjectTypeKeyToIdPrefix + key.InternalKey(), nil
|
||||
}).Maybe()
|
||||
|
||||
// when
|
||||
marketObjects["_otpage"].Fields["recommendedRelations"] = pbtypes.StringList([]string{"_brname", "_brorigin"})
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, rel, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("recommendedRelations list is not updated", func(t *testing.T) {
|
||||
// given
|
||||
rel := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyRevision.String(): pbtypes.Int64(2),
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_otpage"),
|
||||
bundle.RelationKeyUniqueKey.String(): pbtypes.String("ot-page"),
|
||||
bundle.RelationKeyRecommendedRelations.String(): pbtypes.StringList([]string{"rel-name", "rel-tag"}),
|
||||
}}
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().DeriveObjectID(mock.Anything, mock.Anything).RunAndReturn(func(_ context.Context, key domain.UniqueKey) (string, error) {
|
||||
return addr.ObjectTypeKeyToIdPrefix + key.InternalKey(), nil
|
||||
}).Maybe()
|
||||
|
||||
// when
|
||||
marketObjects["_otpage"].Fields["recommendedRelations"] = pbtypes.StringList([]string{"_brname", "_brtag"})
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, rel, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, toRevise)
|
||||
})
|
||||
|
||||
t.Run("recommendedRelations list is updated by not system relations", func(t *testing.T) {
|
||||
// given
|
||||
rel := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyRevision.String(): pbtypes.Int64(2),
|
||||
bundle.RelationKeySourceObject.String(): pbtypes.String("_otpage"),
|
||||
bundle.RelationKeyUniqueKey.String(): pbtypes.String("ot-page"),
|
||||
bundle.RelationKeyRecommendedRelations.String(): pbtypes.StringList([]string{"rel-name"}),
|
||||
}}
|
||||
space := mock_space.NewMockSpace(t)
|
||||
space.EXPECT().DeriveObjectID(mock.Anything, mock.Anything).RunAndReturn(func(_ context.Context, key domain.UniqueKey) (string, error) {
|
||||
return addr.ObjectTypeKeyToIdPrefix + key.InternalKey(), nil
|
||||
}).Maybe()
|
||||
|
||||
// when
|
||||
marketObjects["_otpage"].Fields["recommendedRelations"] = pbtypes.StringList([]string{"_brname", "_brtag"})
|
||||
toRevise, err := reviseSystemObject(ctx, log, space, rel, marketObjects)
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, toRevise)
|
||||
})
|
||||
}
|
||||
|
|
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue