mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-11 18:20:33 +09:00
Merge pull request #1769 from anyproto/go-4408-unify-date-object-creation
GO-4408 Unify Date object creation
This commit is contained in:
commit
31e066d088
18 changed files with 398 additions and 287 deletions
|
@ -17,8 +17,8 @@ import (
|
|||
"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/database"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
|
||||
"github.com/anyproto/anytype-heart/space/spacecore/typeprovider"
|
||||
"github.com/anyproto/anytype-heart/util/dateutil"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
"github.com/anyproto/anytype-heart/util/slice"
|
||||
)
|
||||
|
@ -121,7 +121,7 @@ func generateFilter(value *types.Value) func(v *types.Value) bool {
|
|||
return equalOrHasFilter
|
||||
}
|
||||
|
||||
start, err := dateIDToDayStart(stringValue)
|
||||
start, err := dateutil.ParseDateId(stringValue)
|
||||
if err != nil {
|
||||
log.Error("failed to convert date id to day start", zap.Error(err))
|
||||
return equalOrHasFilter
|
||||
|
@ -139,10 +139,3 @@ func generateFilter(value *types.Value) func(v *types.Value) bool {
|
|||
return equalOrHasFilter(v)
|
||||
}
|
||||
}
|
||||
|
||||
func dateIDToDayStart(id string) (time.Time, error) {
|
||||
if !strings.HasPrefix(id, addr.DatePrefix) {
|
||||
return time.Time{}, fmt.Errorf("invalid id: date prefix not found")
|
||||
}
|
||||
return time.Parse("2006-01-02", strings.TrimPrefix(id, addr.DatePrefix))
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"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/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/dateutil"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
|
@ -58,7 +58,7 @@ func TestService_ListRelationsWithValue(t *testing.T) {
|
|||
{
|
||||
bundle.RelationKeyId: pbtypes.String("obj2"),
|
||||
bundle.RelationKeySpaceId: pbtypes.String(spaceId),
|
||||
bundle.RelationKeyName: pbtypes.String(addr.TimeToID(now)),
|
||||
bundle.RelationKeyName: pbtypes.String(dateutil.TimeToDateId(now)),
|
||||
bundle.RelationKeyCreatedDate: pbtypes.Int64(now.Add(-24*time.Hour - 5*time.Minute).Unix()),
|
||||
bundle.RelationKeyAddedDate: pbtypes.Int64(now.Add(-24*time.Hour - 3*time.Minute).Unix()),
|
||||
bundle.RelationKeyLastModifiedDate: pbtypes.Int64(now.Add(-1 * time.Minute).Unix()),
|
||||
|
@ -72,7 +72,7 @@ func TestService_ListRelationsWithValue(t *testing.T) {
|
|||
bundle.RelationKeyLastModifiedDate: pbtypes.Int64(now.Unix()),
|
||||
bundle.RelationKeyIsFavorite: pbtypes.Bool(true),
|
||||
bundle.RelationKeyCoverX: pbtypes.Int64(300),
|
||||
bundle.RelationKeyMentions: pbtypes.StringList([]string{addr.TimeToID(now), addr.TimeToID(now.Add(-24 * time.Hour))}),
|
||||
bundle.RelationKeyMentions: pbtypes.StringList([]string{dateutil.TimeToDateId(now), dateutil.TimeToDateId(now.Add(-24 * time.Hour))}),
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -86,13 +86,13 @@ func TestService_ListRelationsWithValue(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
"date object - today",
|
||||
pbtypes.String(addr.TimeToID(now)),
|
||||
pbtypes.String(dateutil.TimeToDateId(now)),
|
||||
[]string{bundle.RelationKeyAddedDate.String(), bundle.RelationKeyCreatedDate.String(), bundle.RelationKeyLastModifiedDate.String(), bundle.RelationKeyMentions.String(), bundle.RelationKeyName.String()},
|
||||
[]int64{1, 2, 3, 1, 1},
|
||||
},
|
||||
{
|
||||
"date object - yesterday",
|
||||
pbtypes.String(addr.TimeToID(now.Add(-24 * time.Hour))),
|
||||
pbtypes.String(dateutil.TimeToDateId(now.Add(-24 * time.Hour))),
|
||||
[]string{bundle.RelationKeyAddedDate.String(), bundle.RelationKeyCreatedDate.String(), bundle.RelationKeyMentions.String()},
|
||||
[]int64{1, 1, 1},
|
||||
},
|
||||
|
|
|
@ -46,6 +46,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/threads"
|
||||
"github.com/anyproto/anytype-heart/space/spacecore/storage/sqlitestorage"
|
||||
"github.com/anyproto/anytype-heart/util/anonymize"
|
||||
"github.com/anyproto/anytype-heart/util/dateutil"
|
||||
"github.com/anyproto/anytype-heart/util/internalflag"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
"github.com/anyproto/anytype-heart/util/slice"
|
||||
|
@ -461,10 +462,7 @@ func (sb *smartBlock) fetchMeta() (details []*model.ObjectViewDetailsSet, err er
|
|||
depIds := sb.dependentSmartIds(sb.includeRelationObjectsAsDependents, true, true)
|
||||
sb.setDependentIDs(depIds)
|
||||
|
||||
perSpace, err := sb.partitionIdsBySpace(sb.depIds)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("partiton by space: %w", err)
|
||||
}
|
||||
perSpace := sb.partitionIdsBySpace(sb.depIds)
|
||||
|
||||
recordsCh := make(chan *types.Struct, 10)
|
||||
sb.recordsSub = database.NewSubscription(nil, recordsCh)
|
||||
|
@ -513,9 +511,14 @@ func (sb *smartBlock) fetchMeta() (details []*model.ObjectViewDetailsSet, err er
|
|||
return
|
||||
}
|
||||
|
||||
func (sb *smartBlock) partitionIdsBySpace(ids []string) (map[string][]string, error) {
|
||||
func (sb *smartBlock) partitionIdsBySpace(ids []string) map[string][]string {
|
||||
perSpace := map[string][]string{}
|
||||
for _, id := range ids {
|
||||
if _, parseErr := dateutil.ParseDateId(id); parseErr == nil {
|
||||
perSpace[sb.space.Id()] = append(perSpace[sb.space.Id()], id)
|
||||
continue
|
||||
}
|
||||
|
||||
spaceId, err := sb.spaceIdResolver.ResolveSpaceID(id)
|
||||
if errors.Is(err, sqlitestorage.ErrObjectNotFound) || errors.Is(err, badger.ErrKeyNotFound) {
|
||||
perSpace[sb.space.Id()] = append(perSpace[sb.space.Id()], id)
|
||||
|
@ -529,7 +532,7 @@ func (sb *smartBlock) partitionIdsBySpace(ids []string) (map[string][]string, er
|
|||
}
|
||||
perSpace[spaceId] = append(perSpace[spaceId], id)
|
||||
}
|
||||
return perSpace, nil
|
||||
return perSpace
|
||||
}
|
||||
|
||||
func (sb *smartBlock) Lock() {
|
||||
|
@ -861,10 +864,7 @@ func (sb *smartBlock) CheckSubscriptions() (changed bool) {
|
|||
}
|
||||
newIDs := sb.recordsSub.Subscribe(sb.depIds)
|
||||
|
||||
perSpace, err := sb.partitionIdsBySpace(newIDs)
|
||||
if err != nil {
|
||||
log.Errorf("partiton by space error: %v", err)
|
||||
}
|
||||
perSpace := sb.partitionIdsBySpace(newIDs)
|
||||
|
||||
for spaceId, ids := range perSpace {
|
||||
spaceIndex := sb.objectStore.SpaceIndex(spaceId)
|
||||
|
|
|
@ -2,13 +2,12 @@ package block
|
|||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo/bson"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/import/notion/api"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/dateutil"
|
||||
textUtil "github.com/anyproto/anytype-heart/util/text"
|
||||
)
|
||||
|
||||
|
@ -240,7 +239,7 @@ func (t *TextObject) handleDateMention(rt api.RichText,
|
|||
if rt.Mention.Date.End != "" {
|
||||
textDate = rt.Mention.Date.End
|
||||
}
|
||||
date, err := time.Parse(DateMentionTimeFormat, textDate)
|
||||
date, err := dateutil.ParseDateId(textDate)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -253,7 +252,7 @@ func (t *TextObject) handleDateMention(rt api.RichText,
|
|||
To: int32(to),
|
||||
},
|
||||
Type: model.BlockContentTextMark_Mention,
|
||||
Param: addr.TimeToID(date),
|
||||
Param: dateutil.TimeToDateId(date),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"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/block/source"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
|
@ -20,6 +21,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/space"
|
||||
"github.com/anyproto/anytype-heart/space/clientspace"
|
||||
"github.com/anyproto/anytype-heart/util/dateutil"
|
||||
"github.com/anyproto/anytype-heart/util/internalflag"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
@ -158,6 +160,8 @@ func (s *service) createObjectInSpace(
|
|||
if pbtypes.GetString(details, bundle.RelationKeyTargetObjectType.String()) == "" {
|
||||
return "", nil, fmt.Errorf("cannot create template without target object")
|
||||
}
|
||||
case bundle.TypeKeyDate:
|
||||
return buildDateObject(space, details)
|
||||
}
|
||||
|
||||
return s.createObjectFromTemplate(ctx, space, []domain.TypeKey{req.ObjectTypeKey}, details, req.TemplateId)
|
||||
|
@ -176,3 +180,33 @@ func (s *service) createObjectFromTemplate(
|
|||
}
|
||||
return s.CreateSmartBlockFromStateInSpace(ctx, space, objectTypeKeys, createState)
|
||||
}
|
||||
|
||||
// buildDateObject does not create real date object. It just builds date object details
|
||||
func buildDateObject(space clientspace.Space, details *types.Struct) (string, *types.Struct, error) {
|
||||
name := pbtypes.GetString(details, bundle.RelationKeyName.String())
|
||||
id, err := dateutil.DateNameToId(name)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to build date object, as its name is invalid: %w", err)
|
||||
}
|
||||
|
||||
typeId, err := space.GetTypeIdByKey(context.Background(), bundle.TypeKeyDate)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to find Date type to build Date object: %w", err)
|
||||
}
|
||||
|
||||
dateSource := source.NewDate(source.DateSourceParams{
|
||||
Id: domain.FullID{
|
||||
ObjectID: id,
|
||||
SpaceID: space.Id(),
|
||||
},
|
||||
DateObjectTypeId: typeId,
|
||||
})
|
||||
|
||||
detailsGetter, ok := dateSource.(source.SourceIdEndodedDetails)
|
||||
if !ok {
|
||||
return "", nil, fmt.Errorf("date object does not implement DetailsFromId")
|
||||
}
|
||||
|
||||
details, err = detailsGetter.DetailsFromId()
|
||||
return id, details, err
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package objectcreator
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -11,10 +12,12 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/block/editor/lastused/mock_lastused"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock/smarttest"
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/space/clientspace"
|
||||
"github.com/anyproto/anytype-heart/space/clientspace/mock_clientspace"
|
||||
"github.com/anyproto/anytype-heart/space/mock_space"
|
||||
"github.com/anyproto/anytype-heart/util/dateutil"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
|
@ -103,4 +106,50 @@ func TestService_CreateObject(t *testing.T) {
|
|||
// then
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("date object creation", func(t *testing.T) {
|
||||
// given
|
||||
f := newFixture(t)
|
||||
f.spaceService.EXPECT().Get(mock.Anything, mock.Anything).Return(f.spc, nil)
|
||||
f.spc.EXPECT().Id().Return(spaceId)
|
||||
f.spc.EXPECT().GetTypeIdByKey(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, key domain.TypeKey) (string, error) {
|
||||
assert.Equal(t, bundle.TypeKeyDate, key)
|
||||
return bundle.TypeKeyDate.URL(), nil
|
||||
})
|
||||
ts := time.Now()
|
||||
name := dateutil.TimeToDateName(ts)
|
||||
|
||||
// when
|
||||
id, details, err := f.service.CreateObject(context.Background(), spaceId, CreateObjectRequest{
|
||||
ObjectTypeKey: bundle.TypeKeyDate,
|
||||
Details: &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyName.String(): pbtypes.String(name),
|
||||
}},
|
||||
})
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, dateutil.TimeToDateId(ts), id)
|
||||
assert.Equal(t, spaceId, pbtypes.GetString(details, bundle.RelationKeySpaceId.String()))
|
||||
assert.Equal(t, bundle.TypeKeyDate.URL(), pbtypes.GetString(details, bundle.RelationKeyType.String()))
|
||||
})
|
||||
|
||||
t.Run("date object creation - invalid name", func(t *testing.T) {
|
||||
// given
|
||||
f := newFixture(t)
|
||||
f.spaceService.EXPECT().Get(mock.Anything, mock.Anything).Return(f.spc, nil)
|
||||
ts := time.Now()
|
||||
name := ts.Format(time.RFC3339)
|
||||
|
||||
// when
|
||||
_, _, err := f.service.CreateObject(context.Background(), spaceId, CreateObjectRequest{
|
||||
ObjectTypeKey: bundle.TypeKeyDate,
|
||||
Details: &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyName.String(): pbtypes.String(name),
|
||||
}},
|
||||
})
|
||||
|
||||
// then
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,9 +13,9 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/block/simple"
|
||||
"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/logging"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/dateutil"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
|
@ -152,7 +152,7 @@ func collectIdsFromDetail(rel *model.RelationLink, det *types.Struct, flags Flag
|
|||
if relInt > 0 {
|
||||
t := time.Unix(relInt, 0)
|
||||
t = t.In(time.Local)
|
||||
ids = append(ids, addr.TimeToID(t))
|
||||
ids = append(ids, dateutil.TimeToDateId(t))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ package source
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
|
||||
|
@ -16,179 +14,104 @@ import (
|
|||
"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/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/dateutil"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
func NewDate(space Space, id domain.FullID) (s Source) {
|
||||
type DateSourceParams struct {
|
||||
Id domain.FullID
|
||||
DateObjectTypeId string
|
||||
}
|
||||
|
||||
func NewDate(params DateSourceParams) (s Source) {
|
||||
return &date{
|
||||
id: id.ObjectID,
|
||||
spaceId: id.SpaceID,
|
||||
space: space,
|
||||
id: params.Id.ObjectID,
|
||||
spaceId: params.Id.SpaceID,
|
||||
typeId: params.DateObjectTypeId,
|
||||
}
|
||||
}
|
||||
|
||||
type date struct {
|
||||
space Space
|
||||
id, spaceId string
|
||||
t time.Time
|
||||
id, spaceId, typeId string
|
||||
}
|
||||
|
||||
func (v *date) ListIds() ([]string, error) {
|
||||
func (d *date) ListIds() ([]string, error) {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (v *date) ReadOnly() bool {
|
||||
func (d *date) ReadOnly() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (v *date) Id() string {
|
||||
return v.id
|
||||
func (d *date) Id() string {
|
||||
return d.id
|
||||
}
|
||||
|
||||
func (v *date) SpaceID() string {
|
||||
if v.space != nil {
|
||||
return v.space.Id()
|
||||
}
|
||||
if v.spaceId != "" {
|
||||
return v.spaceId
|
||||
}
|
||||
return ""
|
||||
func (d *date) SpaceID() string {
|
||||
return d.spaceId
|
||||
}
|
||||
|
||||
func (v *date) Type() smartblock.SmartBlockType {
|
||||
func (d *date) Type() smartblock.SmartBlockType {
|
||||
return smartblock.SmartBlockTypeDate
|
||||
}
|
||||
|
||||
func (v *date) getDetails(ctx context.Context) (*types.Struct, error) {
|
||||
linksRelationId, err := v.space.GetRelationIdByKey(ctx, bundle.RelationKeyLinks)
|
||||
func (d *date) getDetails() (*types.Struct, error) {
|
||||
t, err := dateutil.ParseDateId(d.id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get links relation id: %w", err)
|
||||
}
|
||||
dateTypeId, err := v.space.GetTypeIdByKey(ctx, bundle.TypeKeyDate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get date type id: %w", err)
|
||||
return nil, fmt.Errorf("failed to parse date id: %w", err)
|
||||
}
|
||||
|
||||
return &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyName.String(): pbtypes.String(v.t.Format("02 Jan 2006")),
|
||||
bundle.RelationKeyId.String(): pbtypes.String(v.id),
|
||||
bundle.RelationKeyName.String(): pbtypes.String(dateutil.TimeToDateName(t)),
|
||||
bundle.RelationKeyId.String(): pbtypes.String(d.id),
|
||||
bundle.RelationKeyType.String(): pbtypes.String(d.typeId),
|
||||
bundle.RelationKeyIsReadonly.String(): pbtypes.Bool(true),
|
||||
bundle.RelationKeyIsArchived.String(): pbtypes.Bool(false),
|
||||
bundle.RelationKeyIsHidden.String(): pbtypes.Bool(false),
|
||||
bundle.RelationKeyLayout.String(): pbtypes.Float64(float64(model.ObjectType_date)),
|
||||
bundle.RelationKeyIconEmoji.String(): pbtypes.String("📅"),
|
||||
bundle.RelationKeySpaceId.String(): pbtypes.String(v.SpaceID()),
|
||||
bundle.RelationKeySetOf.String(): pbtypes.StringList([]string{linksRelationId}),
|
||||
bundle.RelationKeyType.String(): pbtypes.String(dateTypeId),
|
||||
bundle.RelationKeySpaceId.String(): pbtypes.String(d.SpaceID()),
|
||||
bundle.RelationKeyTimestamp.String(): pbtypes.Int64(t.Unix()),
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// TODO Fix?
|
||||
func (v *date) DetailsFromId() (*types.Struct, error) {
|
||||
if err := v.parseId(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyName.String(): pbtypes.String(v.t.Format("02 Jan 2006")),
|
||||
bundle.RelationKeyId.String(): pbtypes.String(v.id),
|
||||
bundle.RelationKeyIsReadonly.String(): pbtypes.Bool(true),
|
||||
bundle.RelationKeyIsArchived.String(): pbtypes.Bool(false),
|
||||
bundle.RelationKeyIsHidden.String(): pbtypes.Bool(false),
|
||||
bundle.RelationKeyLayout.String(): pbtypes.Float64(float64(model.ObjectType_date)),
|
||||
bundle.RelationKeyIconEmoji.String(): pbtypes.String("📅"),
|
||||
bundle.RelationKeySpaceId.String(): pbtypes.String(v.SpaceID()),
|
||||
}}, nil
|
||||
func (d *date) DetailsFromId() (*types.Struct, error) {
|
||||
return d.getDetails()
|
||||
}
|
||||
|
||||
func (v *date) parseId() error {
|
||||
t, err := time.Parse("2006-01-02", strings.TrimPrefix(v.id, addr.DatePrefix))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.t = t
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *date) ReadDoc(ctx context.Context, receiver ChangeReceiver, empty bool) (doc state.Doc, err error) {
|
||||
if err = v.parseId(); err != nil {
|
||||
return
|
||||
}
|
||||
s := state.NewDoc(v.id, nil).(*state.State)
|
||||
d, err := v.getDetails(ctx)
|
||||
func (d *date) ReadDoc(context.Context, ChangeReceiver, bool) (doc state.Doc, err error) {
|
||||
details, err := d.getDetails()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dataview := &model.BlockContentOfDataview{
|
||||
Dataview: &model.BlockContentDataview{
|
||||
RelationLinks: []*model.RelationLink{
|
||||
{
|
||||
Key: bundle.RelationKeyName.String(),
|
||||
Format: model.RelationFormat_shorttext,
|
||||
},
|
||||
{
|
||||
Key: bundle.RelationKeyLastModifiedDate.String(),
|
||||
Format: model.RelationFormat_date,
|
||||
},
|
||||
},
|
||||
Views: []*model.BlockContentDataviewView{
|
||||
{
|
||||
Id: "1",
|
||||
Type: model.BlockContentDataviewView_Table,
|
||||
Name: "Date backlinks",
|
||||
Sorts: []*model.BlockContentDataviewSort{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyLastModifiedDate.String(),
|
||||
Type: model.BlockContentDataviewSort_Desc,
|
||||
},
|
||||
},
|
||||
Filters: []*model.BlockContentDataviewFilter{
|
||||
{
|
||||
RelationKey: bundle.RelationKeyLinks.String(),
|
||||
Condition: model.BlockContentDataviewFilter_In,
|
||||
Value: pbtypes.String(v.id),
|
||||
},
|
||||
},
|
||||
Relations: []*model.BlockContentDataviewRelation{
|
||||
{
|
||||
Key: bundle.RelationKeyName.String(),
|
||||
IsVisible: true,
|
||||
},
|
||||
{
|
||||
Key: bundle.RelationKeyLastModifiedDate.String(),
|
||||
IsVisible: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
s := state.NewDoc(d.id, nil).(*state.State)
|
||||
template.InitTemplate(s,
|
||||
template.WithTitle,
|
||||
template.WithDefaultFeaturedRelations,
|
||||
template.WithDataview(dataview, true),
|
||||
template.WithAllBlocksEditsRestricted,
|
||||
)
|
||||
s.SetDetails(d)
|
||||
s.SetDetails(details)
|
||||
s.SetObjectTypeKey(bundle.TypeKeyDate)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (v *date) PushChange(params PushChangeParams) (id string, err error) {
|
||||
func (d *date) PushChange(PushChangeParams) (id string, err error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (v *date) Close() (err error) {
|
||||
func (d *date) Close() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (v *date) Heads() []string {
|
||||
return []string{v.id}
|
||||
func (d *date) Heads() []string {
|
||||
return []string{d.id}
|
||||
}
|
||||
|
||||
func (v *date) GetFileKeysSnapshot() []*pb.ChangeFileKeys {
|
||||
func (d *date) GetFileKeysSnapshot() []*pb.ChangeFileKeys {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *date) GetCreationInfo() (creatorObjectId string, createdDate int64, err error) {
|
||||
func (d *date) GetCreationInfo() (creatorObjectId string, createdDate int64, err error) {
|
||||
return addr.AnytypeProfileId, 0, nil
|
||||
}
|
||||
|
|
|
@ -22,10 +22,13 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/files"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/database"
|
||||
"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/pb/model"
|
||||
"github.com/anyproto/anytype-heart/space/spacecore/storage"
|
||||
"github.com/anyproto/anytype-heart/space/spacecore/typeprovider"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
)
|
||||
|
||||
const CName = "source"
|
||||
|
@ -140,9 +143,16 @@ func (s *service) newSource(ctx context.Context, space Space, id string, buildOp
|
|||
if err == nil {
|
||||
switch st {
|
||||
case smartblock.SmartBlockTypeDate:
|
||||
return NewDate(space, domain.FullID{
|
||||
ObjectID: id,
|
||||
SpaceID: space.Id(),
|
||||
typeId, err := space.GetTypeIdByKey(context.Background(), bundle.TypeKeyDate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find Date type to build Date object: %w", err)
|
||||
}
|
||||
return NewDate(DateSourceParams{
|
||||
Id: domain.FullID{
|
||||
ObjectID: id,
|
||||
SpaceID: space.Id(),
|
||||
},
|
||||
DateObjectTypeId: typeId,
|
||||
}), nil
|
||||
case smartblock.SmartBlockTypeBundledObjectType:
|
||||
return NewBundledObjectType(id), nil
|
||||
|
@ -208,7 +218,27 @@ func (s *service) DetailsFromIdBasedSource(id domain.FullID) (*types.Struct, err
|
|||
if !strings.HasPrefix(id.ObjectID, addr.DatePrefix) {
|
||||
return nil, fmt.Errorf("unsupported id")
|
||||
}
|
||||
ss := NewDate(nil, id)
|
||||
|
||||
records, err := s.objectStore.SpaceIndex(id.SpaceID).Query(database.Query{
|
||||
Filters: []*model.BlockContentDataviewFilter{{
|
||||
Condition: model.BlockContentDataviewFilter_Equal,
|
||||
RelationKey: bundle.RelationKeyUniqueKey.String(),
|
||||
Value: pbtypes.String(bundle.TypeKeyDate.URL()),
|
||||
},
|
||||
}})
|
||||
|
||||
if len(records) != 1 && err == nil {
|
||||
err = fmt.Errorf("expected 1 record, got %d", len(records))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query details of Date type object: %w", err)
|
||||
}
|
||||
|
||||
ss := NewDate(DateSourceParams{
|
||||
Id: id,
|
||||
DateObjectTypeId: pbtypes.GetString(records[0].Details, bundle.RelationKeyId.String()),
|
||||
})
|
||||
defer ss.Close()
|
||||
if v, ok := ss.(SourceIdEndodedDetails); ok {
|
||||
return v.DetailsFromId()
|
||||
|
|
155
core/date/suggest.go
Normal file
155
core/date/suggest.go
Normal file
|
@ -0,0 +1,155 @@
|
|||
package date
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/go-naturaldate/v2"
|
||||
"github.com/araddon/dateparse"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/source"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/database"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
"github.com/anyproto/anytype-heart/space"
|
||||
"github.com/anyproto/anytype-heart/util/dateutil"
|
||||
)
|
||||
|
||||
func EnrichRecordsWithDateSuggestion(
|
||||
ctx context.Context,
|
||||
records []database.Record,
|
||||
req *pb.RpcObjectSearchRequest,
|
||||
store objectstore.ObjectStore,
|
||||
spaceService space.Service,
|
||||
) ([]database.Record, error) {
|
||||
dt := suggestDateForSearch(time.Now(), req.FullText)
|
||||
if dt.IsZero() {
|
||||
return records, nil
|
||||
}
|
||||
|
||||
id := dateutil.TimeToDateId(dt)
|
||||
|
||||
// Don't duplicate search suggestions
|
||||
var found bool
|
||||
for _, r := range records {
|
||||
if r.Details == nil || r.Details.Fields == nil {
|
||||
continue
|
||||
}
|
||||
if v, ok := r.Details.Fields[bundle.RelationKeyId.String()]; ok {
|
||||
if v.GetStringValue() == id {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if found {
|
||||
return records, nil
|
||||
}
|
||||
|
||||
spc, err := spaceService.Get(ctx, req.SpaceId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get space: %w", err)
|
||||
}
|
||||
|
||||
rec, err := makeSuggestedDateRecord(spc, dt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("make date record: %w", err)
|
||||
}
|
||||
f, _ := database.MakeFilters(req.Filters, store.SpaceIndex(req.SpaceId)) //nolint:errcheck
|
||||
if f.FilterObject(rec.Details) {
|
||||
return append([]database.Record{rec}, records...), nil
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func suggestDateForSearch(now time.Time, raw string) time.Time {
|
||||
suggesters := []func() time.Time{
|
||||
func() time.Time {
|
||||
var exprType naturaldate.ExprType
|
||||
t, exprType, err := naturaldate.Parse(raw, now)
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
if exprType == naturaldate.ExprTypeInvalid {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// naturaldate parses numbers without qualifiers (m,s) as hours in 24 hours clock format. It leads to weird behavior
|
||||
// when inputs like "123" represented as "current time + 123 hours"
|
||||
if (exprType & naturaldate.ExprTypeClock24Hour) != 0 {
|
||||
t = time.Time{}
|
||||
}
|
||||
return t
|
||||
},
|
||||
func() time.Time {
|
||||
// Don't use plain numbers, because they will be represented as years
|
||||
if _, err := strconv.Atoi(strings.TrimSpace(raw)); err == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
// todo: use system locale to get preferred date format
|
||||
t, err := dateparse.ParseIn(raw, now.Location(), dateparse.PreferMonthFirst(false))
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return t
|
||||
},
|
||||
}
|
||||
|
||||
var t time.Time
|
||||
for _, s := range suggesters {
|
||||
if t = s(); !t.IsZero() {
|
||||
break
|
||||
}
|
||||
}
|
||||
if t.IsZero() {
|
||||
return t
|
||||
}
|
||||
|
||||
// Sanitize date
|
||||
|
||||
// Date without year
|
||||
if t.Year() == 0 {
|
||||
_, month, day := t.Date()
|
||||
h, m, s := t.Clock()
|
||||
t = time.Date(now.Year(), month, day, h, m, s, 0, t.Location())
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func makeSuggestedDateRecord(spc source.Space, t time.Time) (database.Record, error) {
|
||||
id := dateutil.TimeToDateId(t)
|
||||
|
||||
typeId, err := spc.GetTypeIdByKey(context.Background(), bundle.TypeKeyDate)
|
||||
if err != nil {
|
||||
return database.Record{}, fmt.Errorf("failed to find Date type to build Date object: %w", err)
|
||||
}
|
||||
|
||||
dateSource := source.NewDate(source.DateSourceParams{
|
||||
Id: domain.FullID{
|
||||
ObjectID: id,
|
||||
SpaceID: spc.Id(),
|
||||
},
|
||||
DateObjectTypeId: typeId,
|
||||
})
|
||||
|
||||
v, ok := dateSource.(source.SourceIdEndodedDetails)
|
||||
if !ok {
|
||||
return database.Record{}, fmt.Errorf("source does not implement DetailsFromId")
|
||||
}
|
||||
|
||||
details, err := v.DetailsFromId()
|
||||
if err != nil {
|
||||
return database.Record{}, err
|
||||
}
|
||||
|
||||
return database.Record{
|
||||
Details: details,
|
||||
}, nil
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package core
|
||||
package date
|
||||
|
||||
import (
|
||||
"testing"
|
130
core/object.go
130
core/object.go
|
@ -4,12 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/go-naturaldate/v2"
|
||||
"github.com/araddon/dateparse"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
|
@ -17,12 +12,12 @@ import (
|
|||
importer "github.com/anyproto/anytype-heart/core/block/import"
|
||||
"github.com/anyproto/anytype-heart/core/block/import/common"
|
||||
"github.com/anyproto/anytype-heart/core/block/object/objectgraph"
|
||||
"github.com/anyproto/anytype-heart/core/date"
|
||||
"github.com/anyproto/anytype-heart/core/domain/objectorigin"
|
||||
"github.com/anyproto/anytype-heart/core/indexer"
|
||||
"github.com/anyproto/anytype-heart/core/subscription"
|
||||
"github.com/anyproto/anytype-heart/core/subscription/crossspacesub"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/database"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/ftsearch"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
|
@ -108,7 +103,7 @@ func (mw *Middleware) ObjectSearch(cctx context.Context, req *pb.RpcObjectSearch
|
|||
|
||||
// Add dates only to the first page of search results
|
||||
if req.Offset == 0 {
|
||||
records, err = mw.enrichWithDateSuggestion(cctx, records, req, ds)
|
||||
records, err = date.EnrichRecordsWithDateSuggestion(cctx, records, req, ds, getService[space.Service](mw))
|
||||
if err != nil {
|
||||
return response(pb.RpcObjectSearchResponseError_UNKNOWN_ERROR, nil, err)
|
||||
}
|
||||
|
@ -174,127 +169,6 @@ func (mw *Middleware) ObjectSearchWithMeta(cctx context.Context, req *pb.RpcObje
|
|||
return response(pb.RpcObjectSearchWithMetaResponseError_NULL, resultsModels, nil)
|
||||
}
|
||||
|
||||
func (mw *Middleware) enrichWithDateSuggestion(ctx context.Context, records []database.Record, req *pb.RpcObjectSearchRequest, store objectstore.ObjectStore) ([]database.Record, error) {
|
||||
dt := suggestDateForSearch(time.Now(), req.FullText)
|
||||
if dt.IsZero() {
|
||||
return records, nil
|
||||
}
|
||||
|
||||
id := deriveDateId(dt)
|
||||
|
||||
// Don't duplicate search suggestions
|
||||
var found bool
|
||||
for _, r := range records {
|
||||
if r.Details == nil || r.Details.Fields == nil {
|
||||
continue
|
||||
}
|
||||
if v, ok := r.Details.Fields[bundle.RelationKeyId.String()]; ok {
|
||||
if v.GetStringValue() == id {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if found {
|
||||
return records, nil
|
||||
}
|
||||
|
||||
var rec database.Record
|
||||
rec, err := mw.makeSuggestedDateRecord(ctx, req.SpaceId, dt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("make date record: %w", err)
|
||||
}
|
||||
f, _ := database.MakeFilters(req.Filters, store.SpaceIndex(req.SpaceId)) //nolint:errcheck
|
||||
if f.FilterObject(rec.Details) {
|
||||
return append([]database.Record{rec}, records...), nil
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func suggestDateForSearch(now time.Time, raw string) time.Time {
|
||||
suggesters := []func() time.Time{
|
||||
func() time.Time {
|
||||
var exprType naturaldate.ExprType
|
||||
t, exprType, err := naturaldate.Parse(raw, now)
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
if exprType == naturaldate.ExprTypeInvalid {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// naturaldate parses numbers without qualifiers (m,s) as hours in 24 hours clock format. It leads to weird behavior
|
||||
// when inputs like "123" represented as "current time + 123 hours"
|
||||
if (exprType & naturaldate.ExprTypeClock24Hour) != 0 {
|
||||
t = time.Time{}
|
||||
}
|
||||
return t
|
||||
},
|
||||
func() time.Time {
|
||||
// Don't use plain numbers, because they will be represented as years
|
||||
if _, err := strconv.Atoi(strings.TrimSpace(raw)); err == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
// todo: use system locale to get preferred date format
|
||||
t, err := dateparse.ParseIn(raw, now.Location(), dateparse.PreferMonthFirst(false))
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return t
|
||||
},
|
||||
}
|
||||
|
||||
var t time.Time
|
||||
for _, s := range suggesters {
|
||||
if t = s(); !t.IsZero() {
|
||||
break
|
||||
}
|
||||
}
|
||||
if t.IsZero() {
|
||||
return t
|
||||
}
|
||||
|
||||
// Sanitize date
|
||||
|
||||
// Date without year
|
||||
if t.Year() == 0 {
|
||||
_, month, day := t.Date()
|
||||
h, m, s := t.Clock()
|
||||
t = time.Date(now.Year(), month, day, h, m, s, 0, t.Location())
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func deriveDateId(t time.Time) string {
|
||||
return "_date_" + t.Format("2006-01-02")
|
||||
}
|
||||
|
||||
func (mw *Middleware) makeSuggestedDateRecord(ctx context.Context, spaceID string, t time.Time) (database.Record, error) {
|
||||
id := deriveDateId(t)
|
||||
spc, err := getService[space.Service](mw).Get(ctx, spaceID)
|
||||
if err != nil {
|
||||
return database.Record{}, fmt.Errorf("get space: %w", err)
|
||||
}
|
||||
typeId, err := spc.GetTypeIdByKey(ctx, bundle.TypeKeyDate)
|
||||
if err != nil {
|
||||
return database.Record{}, fmt.Errorf("get date type id: %w", err)
|
||||
}
|
||||
d := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyId.String(): pbtypes.String(id),
|
||||
bundle.RelationKeyName.String(): pbtypes.String(t.Format("02 Jan 2006")),
|
||||
bundle.RelationKeyLayout.String(): pbtypes.Int64(int64(model.ObjectType_date)),
|
||||
bundle.RelationKeyType.String(): pbtypes.String(typeId),
|
||||
bundle.RelationKeyIconEmoji.String(): pbtypes.String("📅"),
|
||||
bundle.RelationKeySpaceId.String(): pbtypes.String(spaceID),
|
||||
}}
|
||||
|
||||
return database.Record{
|
||||
Details: d,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (mw *Middleware) ObjectSearchSubscribe(cctx context.Context, req *pb.RpcObjectSearchSubscribeRequest) *pb.RpcObjectSearchSubscribeResponse {
|
||||
errResponse := func(err error) *pb.RpcObjectSearchSubscribeResponse {
|
||||
r := &pb.RpcObjectSearchSubscribeResponse{
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
const RelationChecksum = "414402e104d3b92fa6045649ffa60f6988979f427cf91ef678c53257a0d83fc4"
|
||||
const RelationChecksum = "d020435326985f2804482fd51f0a5fde92c007683e42a11ce26193c9b2876033"
|
||||
const (
|
||||
RelationKeyTag domain.RelationKey = "tag"
|
||||
RelationKeyCamera domain.RelationKey = "camera"
|
||||
|
@ -143,6 +143,7 @@ const (
|
|||
RelationKeyHasChat domain.RelationKey = "hasChat"
|
||||
RelationKeyChatId domain.RelationKey = "chatId"
|
||||
RelationKeyMentions domain.RelationKey = "mentions"
|
||||
RelationKeyTimestamp domain.RelationKey = "timestamp"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -1847,6 +1848,19 @@ var (
|
|||
ReadOnlyRelation: true,
|
||||
Scope: model.Relation_type,
|
||||
},
|
||||
RelationKeyTimestamp: {
|
||||
|
||||
DataSource: model.Relation_derived,
|
||||
Description: "Unix time representation of date object",
|
||||
Format: model.RelationFormat_date,
|
||||
Hidden: true,
|
||||
Id: "_brtimestamp",
|
||||
Key: "timestamp",
|
||||
Name: "Timestamp",
|
||||
ReadOnly: true,
|
||||
ReadOnlyRelation: true,
|
||||
Scope: model.Relation_type,
|
||||
},
|
||||
RelationKeyToBeDeletedDate: {
|
||||
|
||||
DataSource: model.Relation_account,
|
||||
|
|
|
@ -1350,5 +1350,15 @@
|
|||
"name": "Mentions",
|
||||
"readonly": true,
|
||||
"source": "local"
|
||||
},
|
||||
{
|
||||
"description": "Unix time representation of date object",
|
||||
"format": "date",
|
||||
"hidden": true,
|
||||
"key": "timestamp",
|
||||
"maxCount": 0,
|
||||
"name": "Timestamp",
|
||||
"readonly": true,
|
||||
"source": "derived"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -6,7 +6,7 @@ package bundle
|
|||
|
||||
import domain "github.com/anyproto/anytype-heart/core/domain"
|
||||
|
||||
const SystemRelationsChecksum = "dccec4608e8b4d207055a77d1e475598b6ed9ef177d0d796065ebc6b9e0466f7"
|
||||
const SystemRelationsChecksum = "cebd4ab7522c2ca901215e20861e005c1ad1a6ca2a77d7c1205e9e51edd901db"
|
||||
|
||||
// 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
|
||||
|
@ -81,4 +81,5 @@ var SystemRelations = append(RequiredInternalRelations, []domain.RelationKey{
|
|||
RelationKeyMentions,
|
||||
RelationKeyChatId,
|
||||
RelationKeyHasChat,
|
||||
RelationKeyTimestamp,
|
||||
}...)
|
||||
|
|
|
@ -89,5 +89,6 @@
|
|||
"lastUsedDate",
|
||||
"mentions",
|
||||
"chatId",
|
||||
"hasChat"
|
||||
"hasChat",
|
||||
"timestamp"
|
||||
]
|
||||
|
|
|
@ -3,7 +3,6 @@ package addr
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
@ -44,7 +43,3 @@ func ExtractVirtualSourceType(id string) (model.SmartBlockType, error) {
|
|||
}
|
||||
return 0, fmt.Errorf("sb type '%s' not found", sbTypeName)
|
||||
}
|
||||
|
||||
func TimeToID(t time.Time) string {
|
||||
return DatePrefix + t.Format("2006-01-02")
|
||||
}
|
||||
|
|
33
util/dateutil/util.go
Normal file
33
util/dateutil/util.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package dateutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
|
||||
)
|
||||
|
||||
const (
|
||||
dateIdLayout = "2006-01-02"
|
||||
dateNameLayout = "02 Jan 2006"
|
||||
)
|
||||
|
||||
func TimeToDateId(t time.Time) string {
|
||||
return addr.DatePrefix + t.Format(dateIdLayout)
|
||||
}
|
||||
|
||||
func ParseDateId(id string) (time.Time, error) {
|
||||
return time.Parse(dateIdLayout, strings.TrimPrefix(id, addr.DatePrefix))
|
||||
}
|
||||
|
||||
func TimeToDateName(t time.Time) string {
|
||||
return t.Format(dateNameLayout)
|
||||
}
|
||||
|
||||
func DateNameToId(name string) (string, error) {
|
||||
t, err := time.Parse(dateNameLayout, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return TimeToDateId(t), nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue