From 720f3b3befc501792a83997ad7301acb91e5f3fa Mon Sep 17 00:00:00 2001 From: Alexandr Burdiyan Date: Fri, 8 Sep 2023 14:08:20 +0200 Subject: [PATCH 1/4] fix(backend): use full EIDs in the document APIs --- .../daemon/api/accounts/v1alpha/accounts.go | 6 +- .../daemon/api/documents/v1alpha/changes.go | 2 +- .../api/documents/v1alpha/content_graph.go | 4 +- .../documents/v1alpha/content_graph_test.go | 4 +- .../api/documents/v1alpha/document_model.go | 3 +- .../documents/v1alpha/document_model_test.go | 4 +- .../daemon/api/documents/v1alpha/documents.go | 22 +- .../api/documents/v1alpha/documents_test.go | 4 - backend/daemon/api/groups/v1alpha/groups.go | 4 +- backend/daemon/daemon_e2e_test.go | 2 +- .../documents/v1alpha/documents.pb.go | 270 +++++++++--------- backend/hyper/entity.go | 9 +- backend/hyper/indexing.go | 8 +- backend/mttnet/site.go | 16 +- .../documents/v1alpha/documents_pb.ts | 10 +- proto/documents/v1alpha/documents.proto | 7 +- proto/documents/v1alpha/go.gensum | 4 +- proto/documents/v1alpha/js.gensum | 4 +- 18 files changed, 180 insertions(+), 203 deletions(-) diff --git a/backend/daemon/api/accounts/v1alpha/accounts.go b/backend/daemon/api/accounts/v1alpha/accounts.go index 4ee91f76d2..5df2344053 100644 --- a/backend/daemon/api/accounts/v1alpha/accounts.go +++ b/backend/daemon/api/accounts/v1alpha/accounts.go @@ -284,20 +284,20 @@ func (srv *Server) ListAccounts(ctx context.Context, in *accounts.ListAccountsRe return nil, err } - mine := me.Account().String() + mine := hyper.EntityID("hm://a/" + me.Account().String()) resp := &accounts.ListAccountsResponse{ Accounts: make([]*accounts.Account, 0, len(entities)-1), // all except our own account. } for _, e := range entities { - aid := e.TrimPrefix("hm://a/") + aid := e if aid == mine { continue } draft, err := srv.GetAccount(ctx, &accounts.GetAccountRequest{ - Id: aid, + Id: aid.TrimPrefix("hm://a/"), }) if err != nil { continue diff --git a/backend/daemon/api/documents/v1alpha/changes.go b/backend/daemon/api/documents/v1alpha/changes.go index bede5cd1da..a65e2b31fe 100644 --- a/backend/daemon/api/documents/v1alpha/changes.go +++ b/backend/daemon/api/documents/v1alpha/changes.go @@ -39,7 +39,7 @@ func (api *Server) ListChanges(ctx context.Context, in *documents.ListChangesReq return nil, status.Errorf(codes.InvalidArgument, "must provide document id") } - eid := hyper.EntityID("hm://d/" + in.DocumentId) + eid := hyper.EntityID(in.DocumentId) out := &documents.ListChangesResponse{} diff --git a/backend/daemon/api/documents/v1alpha/content_graph.go b/backend/daemon/api/documents/v1alpha/content_graph.go index 004e9f9db1..90effaa584 100644 --- a/backend/daemon/api/documents/v1alpha/content_graph.go +++ b/backend/daemon/api/documents/v1alpha/content_graph.go @@ -20,7 +20,7 @@ func (srv *Server) ListCitations(ctx context.Context, in *documents.ListCitation return nil, status.Error(codes.InvalidArgument, "must specify document ID") } - targetEntity := "hm://d/" + in.DocumentId + targetEntity := in.DocumentId var backlinks []hypersql.BacklinksForEntityResult if err := srv.blobs.Query(ctx, func(conn *sqlite.Conn) error { @@ -50,7 +50,7 @@ func (srv *Server) ListCitations(ctx context.Context, in *documents.ListCitation resp.Links[i] = &documents.Link{ Source: &documents.LinkNode{ - DocumentId: hyper.EntityID(link.EntitiesEID).TrimPrefix("hm://d/"), + DocumentId: link.EntitiesEID, BlockId: link.BlobAttrsAnchor, Version: src.String(), }, diff --git a/backend/daemon/api/documents/v1alpha/content_graph_test.go b/backend/daemon/api/documents/v1alpha/content_graph_test.go index e8044e6e3f..953d29f00c 100644 --- a/backend/daemon/api/documents/v1alpha/content_graph_test.go +++ b/backend/daemon/api/documents/v1alpha/content_graph_test.go @@ -48,7 +48,7 @@ func TestBacklinks(t *testing.T) { Starts: []int32{0}, Ends: []int32{5}, Attributes: map[string]string{ - "url": "hm://d/" + pub.Document.Id + "?v=" + pub.Version + "#b1", + "url": pub.Document.Id + "?v=" + pub.Version + "#b1", }, }, }, @@ -64,7 +64,7 @@ func TestBacklinks(t *testing.T) { Starts: []int32{0}, Ends: []int32{5}, Attributes: map[string]string{ - "url": "hm://d/" + pub.Document.Id + "?v=" + pub.Version, + "url": pub.Document.Id + "?v=" + pub.Version, }, }, }, diff --git a/backend/daemon/api/documents/v1alpha/document_model.go b/backend/daemon/api/documents/v1alpha/document_model.go index 7d18c23014..1aeddf6131 100644 --- a/backend/daemon/api/documents/v1alpha/document_model.go +++ b/backend/daemon/api/documents/v1alpha/document_model.go @@ -327,8 +327,7 @@ func (dm *docModel) hydrate(ctx context.Context, blobs *hyper.Storage) (*documen } docpb := &documents.Document{ - Id: e.ID().TrimPrefix("hm://d/"), - Eid: string(e.ID()), + Id: string(e.ID()), CreateTime: timestamppb.New(time.Unix(int64(createTime), 0)), Author: core.Principal(owner).String(), } diff --git a/backend/daemon/api/documents/v1alpha/document_model_test.go b/backend/daemon/api/documents/v1alpha/document_model_test.go index df6d7d2de5..1a6b9f61d3 100644 --- a/backend/daemon/api/documents/v1alpha/document_model_test.go +++ b/backend/daemon/api/documents/v1alpha/document_model_test.go @@ -344,11 +344,11 @@ func newTestDocModel(t *testing.T, blobs *hyper.Storage, account, device core.Ke ts := clock.Now() now := ts.Time().Unix() - id, nonce := hyper.NewUnforgeableID(account.Principal(), nil, now) + id, nonce := hyper.NewUnforgeableID("", account.Principal(), nil, now) delegation, err := daemon.Register(context.Background(), blobs, account, device.PublicKey, time.Now()) require.NoError(t, err) - entity := hyper.NewEntity(hyper.EntityID("hm://d/" + id)) + entity := hyper.NewEntity(hyper.EntityID(id)) dm, err := newDocModel(entity, device, delegation) require.NoError(t, err) diff --git a/backend/daemon/api/documents/v1alpha/documents.go b/backend/daemon/api/documents/v1alpha/documents.go index d13b3d2873..d031940437 100644 --- a/backend/daemon/api/documents/v1alpha/documents.go +++ b/backend/daemon/api/documents/v1alpha/documents.go @@ -69,7 +69,7 @@ func (api *Server) CreateDraft(ctx context.Context, in *documents.CreateDraftReq } if in.ExistingDocumentId != "" { - eid := hyper.EntityID("hm://d/" + in.ExistingDocumentId) + eid := hyper.EntityID(in.ExistingDocumentId) _, err := api.blobs.FindDraft(ctx, eid) if err == nil { @@ -115,8 +115,8 @@ func (api *Server) CreateDraft(ctx context.Context, in *documents.CreateDraftReq ts := clock.Now() now := ts.Time().Unix() - docid, nonce := hyper.NewUnforgeableID(me.Account().Principal(), nil, now) - eid := hyper.EntityID("hm://d/" + docid) + docid, nonce := hyper.NewUnforgeableID("hm://d/", me.Account().Principal(), nil, now) + eid := hyper.EntityID(docid) entity := hyper.NewEntityWithClock(eid, clock) @@ -160,7 +160,7 @@ func (api *Server) UpdateDraft(ctx context.Context, in *documents.UpdateDraftReq return nil, err } - eid := hyper.EntityID("hm://d/" + in.DocumentId) + eid := hyper.EntityID(in.DocumentId) draft, err := api.blobs.LoadDraft(ctx, eid) if err != nil { @@ -232,7 +232,7 @@ func (api *Server) GetDraft(ctx context.Context, in *documents.GetDraftRequest) return nil, err } - eid := hyper.EntityID("hm://d/" + in.DocumentId) + eid := hyper.EntityID(in.DocumentId) entity, err := api.blobs.LoadDraftEntity(ctx, eid) if err != nil { @@ -267,7 +267,7 @@ func (api *Server) ListDrafts(ctx context.Context, in *documents.ListDraftsReque } for _, e := range entities { - docid := e.TrimPrefix("hm://d/") + docid := string(e) draft, err := api.GetDraft(ctx, &documents.GetDraftRequest{ DocumentId: docid, }) @@ -286,7 +286,7 @@ func (api *Server) PublishDraft(ctx context.Context, in *documents.PublishDraftR return nil, status.Errorf(codes.InvalidArgument, "must specify document ID to get the draft") } - eid := hyper.EntityID("hm://d/" + in.DocumentId) + eid := hyper.EntityID(in.DocumentId) oid, err := eid.CID() if err != nil { @@ -317,7 +317,7 @@ func (api *Server) DeleteDraft(ctx context.Context, in *documents.DeleteDraftReq return nil, status.Errorf(codes.InvalidArgument, "must specify draft ID to delete") } - eid := hyper.EntityID("hm://d/" + in.DocumentId) + eid := hyper.EntityID(in.DocumentId) if err := api.blobs.DeleteDraft(ctx, eid); err != nil { return nil, err @@ -332,7 +332,7 @@ func (api *Server) GetPublication(ctx context.Context, in *documents.GetPublicat return nil, status.Errorf(codes.InvalidArgument, "must specify document ID to get the draft") } - eid := hyper.EntityID("hm://d/" + in.DocumentId) + eid := hyper.EntityID(in.DocumentId) version := hyper.Version(in.Version) pub, err := api.loadPublication(ctx, eid, version, in.TrustedOnly) @@ -421,7 +421,7 @@ func (api *Server) DeletePublication(ctx context.Context, in *documents.DeletePu return nil, status.Errorf(codes.InvalidArgument, "must specify publication ID to delete") } - eid := hyper.EntityID("hm://d/" + in.DocumentId) + eid := hyper.EntityID(in.DocumentId) if err := api.blobs.DeleteEntity(ctx, eid); err != nil { return nil, err @@ -442,7 +442,7 @@ func (api *Server) ListPublications(ctx context.Context, in *documents.ListPubli } for _, e := range entities { - docid := e.TrimPrefix("hm://d/") + docid := string(e) pub, err := api.GetPublication(ctx, &documents.GetPublicationRequest{ DocumentId: docid, LocalOnly: true, diff --git a/backend/daemon/api/documents/v1alpha/documents_test.go b/backend/daemon/api/documents/v1alpha/documents_test.go index ecbf54bc36..1e430abb5f 100644 --- a/backend/daemon/api/documents/v1alpha/documents_test.go +++ b/backend/daemon/api/documents/v1alpha/documents_test.go @@ -316,7 +316,6 @@ func TestAPIUpdateDraft_Complex(t *testing.T) { want := &documents.Document{ Id: draft.Id, - Eid: draft.Eid, Author: draft.Author, Editors: []string{draft.Author}, Title: "Hello Drafts V2", @@ -373,7 +372,6 @@ func TestAPIUpdateDraft_Complex(t *testing.T) { want := &documents.Document{ Id: draft.Id, - Eid: draft.Eid, Author: draft.Author, Editors: []string{draft.Author}, Title: "Hello Drafts V2", @@ -424,7 +422,6 @@ func TestAPIUpdateDraft_Complex(t *testing.T) { want := &documents.Document{ Id: draft.Id, - Eid: draft.Eid, Author: draft.Author, Editors: []string{draft.Author}, Title: "Hello Drafts V2", @@ -573,7 +570,6 @@ func TestAPIUpdateDraft_WithList(t *testing.T) { want := &documents.Document{ Id: draft.Id, - Eid: draft.Eid, Title: "My new document title", Author: draft.Author, Editors: []string{draft.Author}, diff --git a/backend/daemon/api/groups/v1alpha/groups.go b/backend/daemon/api/groups/v1alpha/groups.go index 9747681ea0..c79ee6546e 100644 --- a/backend/daemon/api/groups/v1alpha/groups.go +++ b/backend/daemon/api/groups/v1alpha/groups.go @@ -59,8 +59,8 @@ func (srv *Server) CreateGroup(ctx context.Context, in *groups.CreateGroupReques ts := clock.Now() createTime := ts.Time().Unix() - id, nonce := hyper.NewUnforgeableID(me.Account().Principal(), nil, createTime) - eid := hyper.EntityID("hm://g/" + id) + id, nonce := hyper.NewUnforgeableID("hm://g/", me.Account().Principal(), nil, createTime) + eid := hyper.EntityID(id) e := hyper.NewEntityWithClock(eid, clock) patch := map[string]any{ diff --git a/backend/daemon/daemon_e2e_test.go b/backend/daemon/daemon_e2e_test.go index 9a76d12e8f..c1820bb1c0 100644 --- a/backend/daemon/daemon_e2e_test.go +++ b/backend/daemon/daemon_e2e_test.go @@ -692,7 +692,7 @@ func TestBug_ListObjectsMustHaveCausalOrder(t *testing.T) { var found *p2p.Object seen := map[cid.Cid]struct{}{} for _, obj := range list.Objects { - if obj.Id == "hm://d/"+pub.Document.Id { + if obj.Id == pub.Document.Id { found = obj } for _, ch := range obj.ChangeIds { diff --git a/backend/genproto/documents/v1alpha/documents.pb.go b/backend/genproto/documents/v1alpha/documents.pb.go index 7a65d0fe01..f41ee5c0c8 100644 --- a/backend/genproto/documents/v1alpha/documents.pb.go +++ b/backend/genproto/documents/v1alpha/documents.pb.go @@ -914,8 +914,6 @@ type Document struct { // Permanent ID of the document. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // Entity ID of the document. - Eid string `protobuf:"bytes,12,opt,name=eid,proto3" json:"eid,omitempty"` // Title of the document. Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` // Output only. Author ID of the document. @@ -974,13 +972,6 @@ func (x *Document) GetId() string { return "" } -func (x *Document) GetEid() string { - if x != nil { - return x.Eid - } - return "" -} - func (x *Document) GetTitle() string { if x != nil { return x.Title @@ -1477,146 +1468,145 @@ var file_documents_v1alpha_documents_proto_rawDesc = []byte{ 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x52, 0x08, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x8c, 0x03, 0x0a, 0x08, 0x44, + 0x52, 0x08, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0xfa, 0x02, 0x0a, 0x08, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x69, 0x64, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, - 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x77, 0x65, 0x62, 0x5f, 0x75, - 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x77, 0x65, 0x62, 0x55, 0x72, 0x6c, - 0x12, 0x18, 0x0a, 0x07, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x07, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x44, 0x0a, 0x08, 0x63, 0x68, - 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, - 0x12, 0x3b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3b, 0x0a, - 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x70, 0x75, - 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x77, 0x65, 0x62, 0x5f, 0x75, 0x72, 0x6c, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x77, 0x65, 0x62, 0x55, 0x72, 0x6c, 0x12, 0x18, + 0x0a, 0x07, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x07, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x44, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, + 0x64, 0x72, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6d, + 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x12, 0x3b, + 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x70, 0x75, - 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x8d, 0x01, 0x0a, 0x09, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x3a, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, - 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x12, 0x44, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, - 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x52, - 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x22, 0xcf, 0x02, 0x0a, 0x05, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x72, - 0x65, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x54, 0x0a, - 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x73, 0x12, 0x4b, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, - 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x3d, 0x0a, 0x0f, - 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf8, 0x01, 0x0a, 0x0a, - 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x10, - 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, - 0x12, 0x59, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x73, 0x68, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x73, 0x68, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x8d, 0x01, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x3a, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x05, 0x52, 0x04, 0x65, 0x6e, 0x64, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0x8b, 0x05, 0x0a, 0x06, 0x44, 0x72, 0x61, 0x66, 0x74, - 0x73, 0x12, 0x69, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, - 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, - 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x12, 0x44, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x58, 0x0a, 0x0b, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x31, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x63, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x44, 0x72, 0x61, - 0x66, 0x74, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, - 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, - 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x74, 0x0a, 0x0b, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x71, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x12, - 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, + 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x63, + 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x22, 0xcf, 0x02, 0x0a, 0x05, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x54, 0x0a, 0x0a, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x73, 0x12, 0x4b, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, + 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf8, 0x01, 0x0a, 0x0a, 0x41, 0x6e, + 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x03, + 0x72, 0x65, 0x66, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x59, + 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, + 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x73, 0x12, 0x12, 0x0a, 0x04, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, + 0x04, 0x65, 0x6e, 0x64, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x32, 0x8b, 0x05, 0x0a, 0x06, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x12, + 0x69, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x31, + 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, - 0x72, 0x61, 0x66, 0x74, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, - 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x72, 0x61, 0x66, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, + 0x61, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x58, 0x0a, 0x0b, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, + 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x63, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, + 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, + 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x2e, 0x47, 0x65, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, + 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x74, 0x0a, 0x0b, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x32, 0xee, 0x02, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x72, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, - 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, + 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6f, + 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x71, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, - 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x64, 0x0a, 0x11, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, - 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, - 0x83, 0x01, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x36, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, - 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x63, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, + 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x72, 0x61, + 0x66, 0x74, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, + 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x32, 0xee, 0x02, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x72, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, + 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6f, + 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x64, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x36, 0x5a, 0x34, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, - 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x3b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x83, 0x01, + 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x36, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, + 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x63, 0x6f, 0x6d, + 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x42, 0x36, 0x5a, 0x34, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2f, 0x62, + 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x3b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/backend/hyper/entity.go b/backend/hyper/entity.go index bd14dd0704..b577c91134 100644 --- a/backend/hyper/entity.go +++ b/backend/hyper/entity.go @@ -573,7 +573,9 @@ type ParsedBlob[T any] struct { // NewUnforgeableID creates a new random ID that is verifiable with the author's public key. // It return the ID and the nonce. The nonce argument can be nil in which case a new nonce will be created. // Otherwise the same nonce will be returned. -func NewUnforgeableID(author core.Principal, nonce []byte, ts int64) (string, []byte) { +func NewUnforgeableID(prefix string, author core.Principal, nonce []byte, ts int64) (string, []byte) { + const hashSize = 22 + if nonce == nil { nonce = make([]byte, 16) _, err := rand.Read(nonce) @@ -603,8 +605,9 @@ func NewUnforgeableID(author core.Principal, nonce []byte, ts int64) (string, [] panic(err) } - // Using last 22 characters to avoid multibase prefix. + // Using last [hashSize] characters to avoid multibase prefix, + // and reduce the size of the resulting ID. // We don't use full hash digest here, to make our IDs shorter. // But it should have enough collision resistance for our purpose. - return base[len(base)-22:], nonce + return prefix + base[len(base)-hashSize:], nonce } diff --git a/backend/hyper/indexing.go b/backend/hyper/indexing.go index de9fc75303..40fdb21d38 100644 --- a/backend/hyper/indexing.go +++ b/backend/hyper/indexing.go @@ -340,8 +340,8 @@ func (bs *Storage) indexGroupChange(conn *sqlite.Conn, blobID int64, author core return fmt.Errorf("owner field in the create change must correspond with the author of the change") } - id, _ := NewUnforgeableID(ownerField, nonce, int64(ct)) - if ch.Entity.TrimPrefix("hm://g/") != id { + id, _ := NewUnforgeableID("hm://g/", ownerField, nonce, int64(ct)) + if ch.Entity != EntityID(id) { return fmt.Errorf("failed to verify group ID %s with a nonce", ch.Entity) } @@ -500,8 +500,8 @@ func (bs *Storage) indexDocumentChange(conn *sqlite.Conn, blobID int64, author c return fmt.Errorf("owner field in the create change must correspond with the author of the change") } - id, _ := NewUnforgeableID(ownerField, nonce, int64(ct)) - if ch.Entity.TrimPrefix("hm://d/") != id { + id, _ := NewUnforgeableID("hm://d/", ownerField, nonce, int64(ct)) + if ch.Entity != EntityID(id) { return fmt.Errorf("failed to verify document ID %s with a nonce", ch.Entity) } diff --git a/backend/mttnet/site.go b/backend/mttnet/site.go index 41fba9e100..ae58a1f06e 100644 --- a/backend/mttnet/site.go +++ b/backend/mttnet/site.go @@ -447,12 +447,12 @@ func (srv *Server) PublishDocument(ctx context.Context, in *site.PublishDocument return nil, fmt.Errorf("can't proxy: local p2p node is not ready yet: %w", err) } - docEntity := hyper.EntityID("hm://d/" + in.DocumentId) + docEntity := hyper.EntityID(in.DocumentId) toSync := []hyper.EntityID{docEntity} for _, ref := range in.ReferencedDocuments { - toSync = append(toSync, hyper.EntityID("hm://d/"+ref.DocumentId)) + toSync = append(toSync, hyper.EntityID(ref.DocumentId)) } ctx, cancel := context.WithTimeout(ctx, time.Duration(7*time.Second)) @@ -533,7 +533,7 @@ func (srv *Server) UnpublishDocument(ctx context.Context, in *site.UnpublishDocu } defer cancel() - eid := hyper.EntityID("hm://d/" + in.DocumentId) + eid := hyper.EntityID(in.DocumentId) records, err := sitesql.GetWebPublicationsByID(conn, string(eid)) if err != nil { @@ -589,17 +589,17 @@ func (srv *Server) ListWebPublications(ctx context.Context, in *site.ListWebPubl } for _, record := range records { - docid := hyper.EntityID(record.EntitiesEID).TrimPrefix("hm://d/") - if docid == record.EntitiesEID { + docid := hyper.EntityID(record.EntitiesEID) + if !docid.HasPrefix("hm://d/") { return nil, fmt.Errorf("BUG: invalid entity ID %q for a document in web publications", record.EntitiesEID) } - if in.DocumentId != "" && in.DocumentId != docid { + if in.DocumentId != "" && in.DocumentId != string(docid) { continue } publications = append(publications, &site.WebPublicationRecord{ - DocumentId: docid, + DocumentId: string(docid), Version: record.WebPublicationsVersion, Hostname: srv.hostname, Path: record.WebPublicationsPath, @@ -638,7 +638,7 @@ func (srv *Server) GetPath(ctx context.Context, in *site.GetPathRequest) (*site. return nil, fmt.Errorf("Could not get record for path [%s]: %w", in.Path, err) } ret, err := srv.localFunctions.GetPublication(ctx, &site.GetPublicationRequest{ - DocumentId: hyper.EntityID(record.EntitiesEID).TrimPrefix("hm://d/"), + DocumentId: record.EntitiesEID, Version: record.WebPublicationsVersion, LocalOnly: true, }) diff --git a/frontend/packages/shared/src/client/.generated/documents/v1alpha/documents_pb.ts b/frontend/packages/shared/src/client/.generated/documents/v1alpha/documents_pb.ts index 8094880826..833792094b 100644 --- a/frontend/packages/shared/src/client/.generated/documents/v1alpha/documents_pb.ts +++ b/frontend/packages/shared/src/client/.generated/documents/v1alpha/documents_pb.ts @@ -540,7 +540,7 @@ export class GetPublicationRequest extends Message { localOnly = false; /** - * Optional. If true, the returned publication version will be the last change made by a + * Optional. If true, the returned publication version will be the last change made by a * trusted contact. If false (default) then the returned version will be de latest available. * * @generated from field: bool trusted_only = 4; @@ -801,13 +801,6 @@ export class Document extends Message { */ id = ""; - /** - * Entity ID of the document. - * - * @generated from field: string eid = 12; - */ - eid = ""; - /** * Title of the document. * @@ -874,7 +867,6 @@ export class Document extends Message { static readonly typeName = "com.mintter.documents.v1alpha.Document"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - { no: 12, name: "eid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "title", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "author", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 10, name: "web_url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, diff --git a/proto/documents/v1alpha/documents.proto b/proto/documents/v1alpha/documents.proto index c217280e78..c79a86cfbd 100644 --- a/proto/documents/v1alpha/documents.proto +++ b/proto/documents/v1alpha/documents.proto @@ -153,7 +153,7 @@ message GetPublicationRequest { // Optional. If true, only local publications will be found. False by default. bool local_only = 3; - // Optional. If true, the returned publication version will be the last change made by a + // Optional. If true, the returned publication version will be the last change made by a // trusted contact. If false (default) then the returned version will be de latest available. bool trusted_only = 4; } @@ -176,7 +176,7 @@ message ListPublicationsRequest { // Optional. Whether to return trusted publications only // or including all publications regardless of the trusted state // By default, it returns all publications (trusted_only = false) - bool trusted_only = 3; + bool trusted_only = 3; } // Response with list of publications. @@ -215,9 +215,6 @@ message Document { // Permanent ID of the document. string id = 1; - // Entity ID of the document. - string eid = 12; - // Title of the document. string title = 2; diff --git a/proto/documents/v1alpha/go.gensum b/proto/documents/v1alpha/go.gensum index 9fcd910388..969784a6eb 100644 --- a/proto/documents/v1alpha/go.gensum +++ b/proto/documents/v1alpha/go.gensum @@ -1,2 +1,2 @@ -srcs: 396da5e08775c9174c2887c9a4ecca46 -outs: db7f831158afd47fbc5a83d87a06bd9c +srcs: 17b10ff856231d5d09fd2df7fa9afbbf +outs: 31fd4b337231a020a1588aa7672399b9 diff --git a/proto/documents/v1alpha/js.gensum b/proto/documents/v1alpha/js.gensum index e0235608d2..5a9aa542ae 100644 --- a/proto/documents/v1alpha/js.gensum +++ b/proto/documents/v1alpha/js.gensum @@ -1,2 +1,2 @@ -srcs: 396da5e08775c9174c2887c9a4ecca46 -outs: 5e9aafe184d74bafebc899336491ebca +srcs: 17b10ff856231d5d09fd2df7fa9afbbf +outs: b6240443782c1535395a5bf4296aac3a From 7b8afb34f3670542564334b7c4028640afc2891d Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Wed, 13 Sep 2023 08:24:52 -0700 Subject: [PATCH 2/4] Front-end ID reorganization --- frontend/apps/site/account-page.tsx | 7 +- .../apps/site/pages/g/[groupId]/index.tsx | 24 +- frontend/apps/site/publication-metadata.tsx | 18 +- frontend/apps/site/publication-page.tsx | 66 +++--- frontend/apps/site/server/routers/_app.ts | 44 ++-- .../app/src/components/quick-switcher.tsx | 26 +-- .../src/components/rightside-block-widget.tsx | 16 +- .../app/src/components/titlebar/common.tsx | 25 +- .../src/components/titlebar/publish-share.tsx | 3 +- .../packages/app/src/editor/embed-block.tsx | 31 ++- .../app/src/editor/hyperdocs-link-plugin.tsx | 4 +- frontend/packages/app/src/models/documents.ts | 11 +- frontend/packages/app/src/models/groups.ts | 17 +- .../packages/app/src/models/publication.ts | 10 +- frontend/packages/app/src/models/sites.ts | 14 +- frontend/packages/app/src/open-url.ts | 42 +++- frontend/packages/app/src/pages/group.tsx | 14 +- frontend/packages/app/src/pages/site-page.tsx | 17 -- .../helpers/pasteHandler.ts | 12 +- .../src/utils/__tests__/entity-id-url.test.ts | 139 +++++++++++ .../utils/__tests__/get-ids-from-url.test.ts | 75 ------ frontend/packages/shared/src/utils/doc-url.ts | 60 ----- .../shared/src/utils/entity-id-url.ts | 195 ++++++++++++++++ .../packages/shared/src/utils/entity-ids.ts | 0 .../shared/src/utils/get-ids-from-url.ts | 88 ------- .../shared/src/utils/hyperdocs-link.ts | 61 ----- frontend/packages/shared/src/utils/index.ts | 4 +- yarn.lock | 218 ++++++++++-------- 28 files changed, 646 insertions(+), 595 deletions(-) create mode 100644 frontend/packages/shared/src/utils/__tests__/entity-id-url.test.ts delete mode 100644 frontend/packages/shared/src/utils/__tests__/get-ids-from-url.test.ts delete mode 100644 frontend/packages/shared/src/utils/doc-url.ts create mode 100644 frontend/packages/shared/src/utils/entity-id-url.ts delete mode 100644 frontend/packages/shared/src/utils/entity-ids.ts delete mode 100644 frontend/packages/shared/src/utils/get-ids-from-url.ts delete mode 100644 frontend/packages/shared/src/utils/hyperdocs-link.ts diff --git a/frontend/apps/site/account-page.tsx b/frontend/apps/site/account-page.tsx index a038fc689c..c0cf6cae6e 100644 --- a/frontend/apps/site/account-page.tsx +++ b/frontend/apps/site/account-page.tsx @@ -1,4 +1,4 @@ -import {HYPERMEDIA_ACCOUNT_PREFIX} from '@mintter/shared' +import {createHmId} from '@mintter/shared' import { Avatar, Heading, @@ -52,10 +52,7 @@ export default function AccountPage({accountId}: {accountId: string}) { return ( - + diff --git a/frontend/apps/site/pages/g/[groupId]/index.tsx b/frontend/apps/site/pages/g/[groupId]/index.tsx index 1537f02dfa..ab1a72b4df 100644 --- a/frontend/apps/site/pages/g/[groupId]/index.tsx +++ b/frontend/apps/site/pages/g/[groupId]/index.tsx @@ -21,15 +21,11 @@ import { View, SimpleTooltip, } from '@mintter/ui' -import {HMGroup, HMPublication} from '@mintter/ui' +import {HMGroup, HMPublication} from '../../../server/json-hm' import {ReactElement} from 'react' import {GestureResponderEvent} from 'react-native' import {Timestamp} from '@bufbuild/protobuf' -import { - HYPERMEDIA_GROUP_PREFIX, - entityIdToSitePath, - formattedDate, -} from '@mintter/shared' +import {formattedDate, unpackHmId, createPublicWebHmUrl} from '@mintter/shared' import {AccountAvatarLink, AccountRow} from 'components/account-row' import {format} from 'date-fns' import {Paragraph} from 'tamagui' @@ -43,9 +39,9 @@ function GroupOwnerSection({owner}: {owner: string}) { ) } -function GroupEditorsSection({groupEid}: {groupEid: string}) { +function GroupEditorsSection({groupId}: {groupId: string}) { const groupMembers = trpc.group.listMembers.useQuery({ - groupEid, + groupId, }) if (!groupMembers.data) return null @@ -143,6 +139,9 @@ function GroupContentItem({ item: {pathName: string; publication: null | HMPublication} group?: null | HMGroup }) { + const groupId = group?.id ? unpackHmId(group?.id) : null + const groupEid = groupId?.eid + if (!groupEid) return null return ( } - href={`${entityIdToSitePath(group?.id)}/${item.pathName}`} + href={`${createPublicWebHmUrl('g', groupEid, {hostname: null})}/${ + item.pathName + }`} /> ) } @@ -181,10 +182,7 @@ export default function GroupPage({ {loadedGroup ? ( <> - + }) { - const groupLink = entityIdToSitePath(group?.id) + const groupId = group?.id ? unpackHmId(group?.id) : null + const groupLink = + groupId?.eid && createPublicWebHmUrl('g', groupId.eid, {hostname: null}) return ( <> {groupLink && ( @@ -209,10 +203,7 @@ export default function PublicationPage({ return ( - + {/* legacy mintter metadata */} @@ -308,19 +299,20 @@ function InlineContentView({ ) } if (content.type === 'link') { - let matchesPattern = matchesHypermediaPattern(content.href) - const href = matchesPattern - ? hmLinkToSitePath(content.href) - : content.href + const href = idToUrl(content.href, null) return ( - - - + href && ( + + + + ) ) } return null @@ -379,21 +371,21 @@ function stripHMLinkPrefix(link: string) { function StaticEmbedBlock({block}: {block: EmbedBlock}) { const reference = block.ref - const [documentId, versionId, blockId] = getIdsfromUrl(reference) + const docId = unpackDocId(reference) const router = useRouter() let embed = trpc.publication.get.useQuery( { - documentId, - versionId, + documentId: docId?.docId, + versionId: docId?.version, }, - {enabled: !!documentId}, + {enabled: !!docId}, ) let content = if (embed.data?.publication?.document?.children) { - if (blockId) { + if (docId?.blockRef) { const blockNode = getBlockNodeById( embed.data?.publication?.document?.children, - blockId, + docId.blockRef, ) content = blockNode ? ( diff --git a/frontend/apps/site/server/routers/_app.ts b/frontend/apps/site/server/routers/_app.ts index 21e76bcf38..de0e931840 100644 --- a/frontend/apps/site/server/routers/_app.ts +++ b/frontend/apps/site/server/routers/_app.ts @@ -4,11 +4,11 @@ import { Changes, ContentGraph, Groups, - HYPERMEDIA_GROUP_PREFIX, Publications, Role, WebPublishing, - getIdsfromUrl, + unpackDocId, + unpackHmId, } from '@mintter/shared' import {localWebsiteClient, transport} from 'client' import {getSiteInfo} from 'get-site-info' @@ -244,15 +244,14 @@ const groupRouter = router({ getGroupPath: procedure .input( z.object({ - groupEid: z.string(), + groupId: z.string(), pathName: z.string(), version: z.string().optional(), }), ) - .query(async ({input: {pathName, groupEid, version}}) => { + .query(async ({input: {pathName, groupId, version}}) => { // todo. get current group content and find the pathName, return the corresponding doc console.log('getting site info') - const groupId = `${HYPERMEDIA_GROUP_PREFIX}${groupEid}` const siteInfo = await groupsClient.listContent({ id: groupId, version, @@ -263,32 +262,31 @@ const groupRouter = router({ }) const item = siteInfo.content[pathName] if (!item) return null - const [documentId, documentVersion] = getIdsfromUrl(item) - if (!documentId || !documentVersion) return null // version is required for group content + const itemId = unpackDocId(item) + if (!itemId?.version) return null // version is required for group content const pub = await publicationsClient.getPublication({ - documentId, - version: documentVersion, + documentId: itemId.docId, + version: itemId.version, }) return { publication: hmPublication(pub), pathName, - documentId, - documentVersion, + documentId: itemId.docId, + documentVersion: itemId.version, groupVersion: version, - groupEid, group: hmGroup(group), } }), get: procedure .input( z.object({ - groupEid: z.string(), + groupId: z.string(), }), ) .query(async ({input}) => { console.log('will getGroup with id', input) const group = await groupsClient.getGroup({ - id: `${HYPERMEDIA_GROUP_PREFIX}${input.groupEid}`, + id: input.groupId, }) console.log('did get group', hmGroup(group)) return { @@ -298,26 +296,26 @@ const groupRouter = router({ listContent: procedure .input( z.object({ - groupEid: z.string(), + groupId: z.string(), }), ) .query(async ({input}) => { const list = await groupsClient.listContent({ - id: `${HYPERMEDIA_GROUP_PREFIX}${input.groupEid}`, + id: input.groupId, }) const listedDocs = await Promise.all( Object.entries(list.content).map(async ([pathName, pubUrl]) => { - const [docId, version] = getIdsfromUrl(pubUrl) - if (!docId || !version) return null // version is required for group content + const docId = unpackDocId(pubUrl) + if (!docId?.version) return null // version is required for group content const pub = await publicationsClient.getPublication({ - documentId: docId, - version, + documentId: docId.docId, + version: docId.version, }) return { pathName, docId, - version, + version: docId.version, publication: hmPublication(pub), } }), @@ -333,12 +331,12 @@ const groupRouter = router({ listMembers: procedure .input( z.object({ - groupEid: z.string(), + groupId: z.string(), }), ) .query(async ({input}) => { const list = await groupsClient.listMembers({ - id: `${HYPERMEDIA_GROUP_PREFIX}${input.groupEid}`, + id: input.groupId, }) return Object.entries(list.members || {}).map(([account, role]) => ({ account, diff --git a/frontend/packages/app/src/components/quick-switcher.tsx b/frontend/packages/app/src/components/quick-switcher.tsx index 119e936583..f22cfffbbb 100644 --- a/frontend/packages/app/src/components/quick-switcher.tsx +++ b/frontend/packages/app/src/components/quick-switcher.tsx @@ -4,11 +4,7 @@ import { } from '@mintter/app/src/models/documents' import {fetchWebLink} from '@mintter/app/src/models/web-links' import {useNavigate} from '@mintter/app/src/utils/navigation' -import { - getIdsfromUrl, - isHypermediaScheme, - matchesHypermediaPattern, -} from '@mintter/shared' +import {isHypermediaScheme} from '@mintter/shared' import {Spinner, YStack} from '@mintter/ui' import {useListen} from '@mintter/app/src/app-context' import {Command} from 'cmdk' @@ -16,6 +12,7 @@ import {useState} from 'react' import {toast} from 'react-hot-toast' import './quick-switcher.css' import {useAppContext} from '@mintter/app/src/app-context' +import {hmIdToAppRoute} from '../open-url' export default function QuickSwitcher() { const [open, setOpen] = useState(false) @@ -57,28 +54,19 @@ export default function QuickSwitcher() { ) : ( No results found. - {(matchesHypermediaPattern(search) || - isHypermediaScheme(search) || + {(isHypermediaScheme(search) || search.startsWith('http://') || search.startsWith('https://')) && ( { - if ( - isHypermediaScheme(search) || - matchesHypermediaPattern(search) - ) { - let [docId, version, block] = getIdsfromUrl(search) + if (isHypermediaScheme(search)) { + const navRoute = hmIdToAppRoute(search) - if (docId) { + if (navRoute) { setOpen(false) - navigate({ - key: 'publication', - documentId: docId, - versionId: version, - blockId: block, - }) + navigate(navRoute) } else { console.log('== ~ QuickSwitcher ~ Querying Web URL', search) setActionPromise( diff --git a/frontend/packages/app/src/components/rightside-block-widget.tsx b/frontend/packages/app/src/components/rightside-block-widget.tsx index efb424d4a5..91cc3d4e41 100644 --- a/frontend/packages/app/src/components/rightside-block-widget.tsx +++ b/frontend/packages/app/src/components/rightside-block-widget.tsx @@ -12,7 +12,7 @@ import {useDocCitations} from '../models/content-graph' import {usePublication} from '../models/documents' import {toast} from '@mintter/app/src/toast' import {copyTextToClipboard} from '@mintter/app/src/copy-to-clipboard' -import {getDocUrl} from '@mintter/shared' +import {createPublicWebHmUrl, unpackHmId} from '@mintter/shared' import {useNavigate, useNavRoute} from '@mintter/app/src/utils/navigation' export function createRightsideBlockWidgetExtension({ @@ -105,9 +105,17 @@ export function RightsideWidget() { }) function onCopy() { - let docUrl = getDocUrl(pub.data) - if (docUrl && spec && spec.id) { - copyTextToClipboard(`${docUrl}#${spec.id}`) + const docId = pub.data?.document?.id + ? unpackHmId(pub.data?.document?.id) + : null + const docVersion = pub.data?.version + if (docId && docId.type === 'd' && docVersion && spec && spec.id) { + copyTextToClipboard( + createPublicWebHmUrl('d', docId.eid, { + version: docVersion, + blockRef: spec.id, + }), + ) toast.success('Block reference copied!') } else { appError('Block reference copy failed', {docUrl, spec}) diff --git a/frontend/packages/app/src/components/titlebar/common.tsx b/frontend/packages/app/src/components/titlebar/common.tsx index e2c2889d1f..fd85a74c86 100644 --- a/frontend/packages/app/src/components/titlebar/common.tsx +++ b/frontend/packages/app/src/components/titlebar/common.tsx @@ -26,8 +26,9 @@ import { Account, DocumentChange, SiteConfig, - getPublicDocUrl, - getPublicEntityUrl, + createPublicWebHmUrl, + unpackHmId, + createHmId, } from '@mintter/shared' import { Back, @@ -149,22 +150,32 @@ export function GroupOptionsButton() { function getReferenceUrlOfRoute(route: NavRoute) { if (route.key === 'group') { - const url = getPublicEntityUrl(route.groupId) // we use this because group IDs are full URLs with hm://g/ prefix, so this more generic conversion is available. - if (!url) return null + const groupId = unpackHmId(route.groupId) + if (!groupId || groupId.type !== 'g') return null + const url = createPublicWebHmUrl('g', groupId.eid) return { label: 'Group URL', url, } } if (route.key === 'publication') { - // docIds currently do not include this hm:// prefix so we use the specific doc url function - const url = getPublicDocUrl(route.documentId, route.versionId) + const docId = unpackHmId(route.documentId) + if (!docId || docId.type !== 'd') return null + const url = createPublicWebHmUrl('d', docId.eid, {version: route.versionId}) if (!url) return null return { label: 'Doc URL', url, } } + if (route.key === 'account') { + const url = createHmId('a', route.accountId) + if (!url) return null + return { + label: 'Account URL', + url, + } + } return null } @@ -210,6 +221,8 @@ export function PageActionButtons(props: TitleBarProps) { , , ] + } else if (route.key === 'account') { + buttonGroup = [] } return {buttonGroup} } diff --git a/frontend/packages/app/src/components/titlebar/publish-share.tsx b/frontend/packages/app/src/components/titlebar/publish-share.tsx index 264f7db277..7659e63abd 100644 --- a/frontend/packages/app/src/components/titlebar/publish-share.tsx +++ b/frontend/packages/app/src/components/titlebar/publish-share.tsx @@ -583,7 +583,7 @@ function PublicationContextButton({route}: {route: PublicationRoute}) { return ( { @@ -731,7 +731,6 @@ export function DraftPublicationButtons() { size="$2" disabled={!isDaemonReady} onPress={() => { - console.log('did start publish', draftId, isDaemonReady) publish.mutate({draftId}) }} theme="green" diff --git a/frontend/packages/app/src/editor/embed-block.tsx b/frontend/packages/app/src/editor/embed-block.tsx index ae0c036858..5c5c4bec5b 100644 --- a/frontend/packages/app/src/editor/embed-block.tsx +++ b/frontend/packages/app/src/editor/embed-block.tsx @@ -16,10 +16,11 @@ import type { import { Block, EmbedBlock as EmbedBlockType, + createHmId, getCIDFromIPFSUrl, - getIdsfromUrl, isHypermediaScheme, serverBlockToEditorInline, + unpackDocId, } from '@mintter/shared' import {SizableText, Spinner, Text, XStack, YStack} from '@mintter/ui' import {AlertCircle} from '@tamagui/lucide-icons' @@ -30,7 +31,7 @@ import {createReactBlockSpec} from '../blocknote-react' import {HMBlockSchema, hmBlockSchema} from '../client/schema' import {BACKEND_FILE_URL} from '../constants' import {usePublication} from '../models/documents' -import {useOpenUrl} from '../open-url' +import {hmIdToAppRoute, useOpenUrl} from '../open-url' function InlineContentView({inline}: {inline: InlineContent[]}) { const openUrl = useOpenUrl() @@ -173,14 +174,9 @@ function EmbedPresentation({ if (editor?.isEditable) { return } - let [documentId, version, blockId] = getIdsfromUrl(block.props.ref) - if (documentId) { - spawn({ - key: 'publication', - documentId, - versionId: version, - blockId, - }) + const route = hmIdToAppRoute(block.props.ref) + if (route) { + spawn(route) } }} > @@ -308,25 +304,24 @@ export const EmbedBlock = createReactBlockSpec({ function useEmbed(ref: string): ReturnType & { content?: BlockNode[] & PartialMessage[] } { - let [documentId, versionId, blockId] = getIdsfromUrl(ref) - + const pubId = unpackDocId(ref) let pubQuery = usePublication({ - documentId, - versionId, - enabled: !!documentId, + documentId: pubId?.docId, + versionId: pubId?.version, + enabled: !!pubId?.docId, }) return useMemo(() => { const data = pubQuery.data if (!data || !data.document) return pubQuery - const selectedBlock = blockId - ? getBlockNodeById(data.document.children, blockId) + const selectedBlock = pubId?.blockRef + ? getBlockNodeById(data.document.children, pubId?.blockRef) : null const embedBlocks = selectedBlock ? [selectedBlock] : data.document.children return {...pubQuery, content: embedBlocks} - }, [pubQuery.data, blockId]) + }, [pubQuery.data, pubId?.blockRef]) } function getBlockNodeById( diff --git a/frontend/packages/app/src/editor/hyperdocs-link-plugin.tsx b/frontend/packages/app/src/editor/hyperdocs-link-plugin.tsx index 11d577d27f..ce321eaa24 100644 --- a/frontend/packages/app/src/editor/hyperdocs-link-plugin.tsx +++ b/frontend/packages/app/src/editor/hyperdocs-link-plugin.tsx @@ -1,4 +1,4 @@ -import {createHyperdocsDocLink} from '@mintter/shared' +import {createHmDocLink} from '@mintter/shared' import {EditorView} from '@tiptap/pm/view' import {Plugin, PluginKey} from 'prosemirror-state' import {fetchWebLink} from '../models/web-links' @@ -86,7 +86,7 @@ async function checkHyperLink( pos, pos + node.textContent.length, view.state.schema.mark('link', { - href: createHyperdocsDocLink( + href: createHmDocLink( res!.documentId!, res?.documentVersion, res?.blockId, diff --git a/frontend/packages/app/src/models/documents.ts b/frontend/packages/app/src/models/documents.ts index 52cee8f73f..fc2fad28d3 100644 --- a/frontend/packages/app/src/models/documents.ts +++ b/frontend/packages/app/src/models/documents.ts @@ -29,12 +29,11 @@ import { Document, DocumentChange, GRPCClient, - HYPERMEDIA_DOCUMENT_PREFIX, ListPublicationsResponse, Publication, isHypermediaScheme, - isMintterGatewayLink, - normalizeHypermediaLink, + isPublicGatewayLink, + normlizeHmId, } from '@mintter/shared' import {useWidgetViewFactory} from '@prosemirror-adapter/react' import { @@ -297,7 +296,7 @@ export function usePublishDraft( await grpcClient.groups.updateGroup({ id: draftGroupContext.groupId, updatedContent: { - [publishPathName]: `${HYPERMEDIA_DOCUMENT_PREFIX}${publishedId}?v=${pub.version}`, + [publishPathName]: `${publishedId}?v=${pub.version}`, }, }) } @@ -1017,8 +1016,8 @@ function extractEmbedRefOfLink(block: any): false | string { if (block.content.length == 1) { let leaf = block.content[0] if (leaf.type == 'link') { - if (isMintterGatewayLink(leaf.href) || isHypermediaScheme(leaf.href)) { - const hmLink = normalizeHypermediaLink(leaf.href) + if (isPublicGatewayLink(leaf.href) || isHypermediaScheme(leaf.href)) { + const hmLink = normlizeHmId(leaf.href) console.log(`== ~ extractEmbedRefOfLink ~ hmLink:`, hmLink) if (hmLink) return hmLink diff --git a/frontend/packages/app/src/models/groups.ts b/frontend/packages/app/src/models/groups.ts index 9fe639f292..451472a3e3 100644 --- a/frontend/packages/app/src/models/groups.ts +++ b/frontend/packages/app/src/models/groups.ts @@ -1,4 +1,4 @@ -import {HYPERMEDIA_DOCUMENT_PREFIX, Role, getIdsfromUrl} from '@mintter/shared' +import {Role, unpackDocId} from '@mintter/shared' import {UseMutationOptions, useMutation, useQuery} from '@tanstack/react-query' import {useGRPCClient, useQueryInvalidator} from '../app-context' import {queryKeys} from './query-keys' @@ -126,7 +126,7 @@ export function usePublishDocToGroup( await grpcClient.groups.updateGroup({ id: groupId, updatedContent: { - [pathName]: `${HYPERMEDIA_DOCUMENT_PREFIX}${docId}?v=${version}`, + [pathName]: `${docId}?v=${version}`, }, }) }, @@ -184,13 +184,6 @@ export function useRenameGroupDoc( const listed = await grpcClient.groups.listContent({ id: groupId, }) - console.log( - 'huh RENAME?!', - groupId, - pathName, - newPathName, - listed.content, - ) const prevPathValue = listed.content[pathName] if (!prevPathValue) throw new Error('Could not find previous path at ' + pathName) @@ -201,10 +194,10 @@ export function useRenameGroupDoc( return prevPathValue }, onSuccess: (result, input, context) => { - const [docId] = getIdsfromUrl(result) + const docId = unpackDocId(result) opts?.onSuccess?.(result, input, context) invalidate([queryKeys.GET_GROUP_CONTENT, input.groupId]) - invalidate([queryKeys.GET_GROUPS_FOR_DOCUMENT, docId]) + invalidate([queryKeys.GET_GROUPS_FOR_DOCUMENT, docId?.docId]) }, }) } @@ -240,7 +233,7 @@ export function useDocumentGroups(documentId?: string) { queryKey: [queryKeys.GET_GROUPS_FOR_DOCUMENT, documentId], queryFn: async () => { const result = await grpcClient.groups.listDocumentGroups({ - documentId: `${HYPERMEDIA_DOCUMENT_PREFIX}${documentId}`, + documentId, }) const resultMap = new Map< string, diff --git a/frontend/packages/app/src/models/publication.ts b/frontend/packages/app/src/models/publication.ts index bc1c225de7..16b2e7d37b 100644 --- a/frontend/packages/app/src/models/publication.ts +++ b/frontend/packages/app/src/models/publication.ts @@ -1,4 +1,4 @@ -import {Publication, getIdsfromUrl} from '@mintter/shared' +import {Publication, unpackDocId} from '@mintter/shared' import {UseQueryOptions} from '@tanstack/react-query' import {usePublication} from './documents' import {PublicationRouteContext} from '../utils/navigation' @@ -36,12 +36,12 @@ export function usePublicationInContext({ // ) queryDocumentId = undefined } - const [groupContentDocId, groupContentVersion] = getIdsfromUrl(contentURL) - if (groupContentDocId !== documentId) + const groupItem = unpackDocId(contentURL) + if (groupItem?.docId !== documentId) throw new Error( - `Group ${groupContextId} content for "${groupContext.pathName}" not match route document id "${documentId}", instead has "${groupContentDocId}"`, + `Group ${groupContextId} content for "${groupContext.pathName}" not match route document id "${documentId}", instead has "${groupItem?.docId}"`, ) - queryVersionId = groupContentVersion + queryVersionId = groupItem?.version } // this avoids querying usePublication if we are in a group context and the group content is not yet loaded, or if it has an error. if the route specifies the version directly we are also ready to query const pubQueryReady = !!queryVersionId || pubContext?.key !== 'group' diff --git a/frontend/packages/app/src/models/sites.ts b/frontend/packages/app/src/models/sites.ts index e450aa0b82..d7e47b7ab1 100644 --- a/frontend/packages/app/src/models/sites.ts +++ b/frontend/packages/app/src/models/sites.ts @@ -1,13 +1,13 @@ import { Block, Document, - getIdsfromUrl, GRPCClient, Member, Member_Role, ReferencedDocument, SiteConfig, SiteInfo, + unpackDocId, } from '@mintter/shared' import {useMutation, UseMutationOptions, useQuery} from '@tanstack/react-query' import {queryKeys} from './query-keys' @@ -22,14 +22,10 @@ function blockExtractReferencedDocs( const docIds: Array = [] block.annotations.forEach((annotation) => { if (annotation.type === 'embed' || annotation.type === 'link') { - let ids - try { - ids = getIdsfromUrl(annotation.attributes.url) - } catch (e) { - // not the best fix for now, but regular URLS are coming through here and we can just skip over them - } - if (ids?.[0]) { - docIds.push({documentId: ids[0], version: ids[1]}) + const ids = unpackDocId(annotation.attributes.url) + + if (ids?.scheme === 'hm') { + docIds.push({documentId: ids?.docId, version: ids?.version}) } } }) diff --git a/frontend/packages/app/src/open-url.ts b/frontend/packages/app/src/open-url.ts index c827e50e4c..70d6d5d5df 100644 --- a/frontend/packages/app/src/open-url.ts +++ b/frontend/packages/app/src/open-url.ts @@ -1,8 +1,35 @@ import {useAppContext} from '@mintter/app/src/app-context' import {NavRoute, useNavigate} from '@mintter/app/src/utils/navigation' -import {getIdsfromUrl, HYPERMEDIA_DOCUMENT_PREFIX} from '@mintter/shared' +import {createHmId, isHypermediaScheme, unpackHmId} from '@mintter/shared' import {useMemo} from 'react' +export function hmIdToAppRoute(hmId: string): NavRoute | undefined { + const hmIds = unpackHmId(hmId) + + let pubRoute: NavRoute | undefined = undefined + if (hmIds?.scheme === 'hm') { + if (hmIds?.type === 'd') { + pubRoute = { + key: 'publication', + documentId: createHmId('d', hmIds.eid), + versionId: hmIds.version, + blockId: hmIds.blockRef, + } + } else if (hmIds?.type === 'g') { + pubRoute = { + key: 'group', + groupId: createHmId('g', hmIds.eid), + } + } else if (hmIds?.type === 'a') { + pubRoute = { + key: 'account', + accountId: hmIds.eid, + } + } + } + return pubRoute +} + export function useOpenUrl() { const {externalOpen} = useAppContext() const spawn = useNavigate('spawn') @@ -11,17 +38,8 @@ export function useOpenUrl() { return (url?: string, newWindow?: boolean) => { if (!url) return - if (url.startsWith(HYPERMEDIA_DOCUMENT_PREFIX)) { - const hmIds = getIdsfromUrl(url) - if (!hmIds[0]) { - throw new Error('Cannot parse Hyperdocs URL without document ID') - } - const pubRoute: NavRoute = { - key: 'publication', - documentId: hmIds[0], - versionId: hmIds[1], - blockId: hmIds[2], - } + const pubRoute = hmIdToAppRoute(url) + if (pubRoute) { if (newWindow) { spawn(pubRoute) } else { diff --git a/frontend/packages/app/src/pages/group.tsx b/frontend/packages/app/src/pages/group.tsx index 8be22b25b3..60125c2da2 100644 --- a/frontend/packages/app/src/pages/group.tsx +++ b/frontend/packages/app/src/pages/group.tsx @@ -1,5 +1,5 @@ import Footer from '@mintter/app/src/components/footer' -import {Document, getIdsfromUrl} from '@mintter/shared' +import {Document, unpackDocId} from '@mintter/shared' import { Button, Container, @@ -263,16 +263,18 @@ export default function GroupPage() { {Object.entries(groupContent.data?.content || {}).map( ([pathName, hmUrl]) => { - const [docId, version] = getIdsfromUrl(hmUrl) + const docId = unpackDocId(hmUrl) if (!docId) return null return ( d.id == docId)} + version={docId?.version} + hasDraft={drafts.data?.documents.find( + (d) => d.id == docId.docId, + )} pathName={pathName} /> ) diff --git a/frontend/packages/app/src/pages/site-page.tsx b/frontend/packages/app/src/pages/site-page.tsx index f72fa154f5..1cc7531e4a 100644 --- a/frontend/packages/app/src/pages/site-page.tsx +++ b/frontend/packages/app/src/pages/site-page.tsx @@ -2,7 +2,6 @@ import {useAccount} from '@mintter/app/src/models/accounts' import {usePublication} from '@mintter/app/src/models/documents' import {useSitePublications} from '@mintter/app/src/models/sites' import {usePopoverState} from '@mintter/app/src/use-popover-state' -import {getDocUrl} from '@mintter/shared' import {useNavigate, useNavRoute} from '@mintter/app/src/utils/navigation' import {useOpenDraft} from '@mintter/app/src/utils/open-draft' import {hostnameStripProtocol} from '@mintter/app/src/utils/site-hostname' @@ -161,22 +160,6 @@ function WebPublicationListItem({ align="start" data-testid="library-item-dropdown-root" > - { - const docUrl = getDocUrl(publication, webPub) - if (!docUrl) return - copyTextToClipboard(docUrl) - toast.success( - `Copied ${hostnameStripProtocol(publishedWebHost)} URL`, - ) - }} - asChild - icon={Copy} - title={`Copy Document URL on ${hostnameStripProtocol( - publishedWebHost, - )}`} - /> { + test('unpacks hm://d/abc', () => { + expect(unpackHmId('hm://d/abc')).toEqual({ + scheme: 'hm', + hostname: undefined, + type: 'd', + eid: 'abc', + version: undefined, + blockRef: undefined, + }) + }) + test('unpacks hm://g/abc?v=123#foo', () => { + expect(unpackHmId('hm://g/abc?v=123#foo')).toEqual({ + scheme: 'hm', + hostname: undefined, + type: 'g', + eid: 'abc', + version: '123', + blockRef: 'foo', + }) + }) + test('unpacks hm://d/foo#bar', () => { + expect(unpackHmId('hm://d/foo#bar')).toEqual({ + scheme: 'hm', + hostname: undefined, + type: 'd', + eid: 'foo', + version: undefined, + blockRef: 'bar', + }) + }) + test('unpacks hm://a/foo?v=bar', () => { + expect(unpackHmId('hm://a/foo?v=bar')).toEqual({ + scheme: 'hm', + hostname: undefined, + type: 'a', + eid: 'foo', + version: 'bar', + blockRef: undefined, + }) + }) + test('unpacks https://foobar.com/d/1?v=2', () => { + expect(unpackHmId('https://foobar.com/d/1?v=2')).toEqual({ + scheme: 'https', + hostname: 'foobar.com', + type: 'd', + eid: '1', + version: '2', + blockRef: undefined, + }) + }) + test('unpacks http://foobar.com/a/1#block', () => { + expect(unpackHmId('http://foobar.com/a/1#block')).toEqual({ + scheme: 'http', + hostname: 'foobar.com', + type: 'a', + eid: '1', + version: undefined, + blockRef: 'block', + }) + }) +}) +describe('parseCustomURL', () => { + test('parseCustomURL hm://a/b?foo=1=&bar=2#block', () => { + expect(parseCustomURL('hm://a/b?foo=1=&bar=2#block')).toEqual({ + scheme: 'hm', + path: ['a', 'b'], + query: {foo: '1', bar: '2'}, + fragment: 'block', + }) + }) +}) +describe('createHmId', () => { + test('creates hm://d/abc', () => { + expect(createHmId('d', 'abc')).toEqual('hm://d/abc') + }) + test('creates hm://g/123?v=foo', () => { + expect(createHmId('g', '123', {version: 'foo'})).toEqual('hm://g/123?v=foo') + }) + test('creates hm://d/123#block', () => { + expect(createHmId('d', '123', {blockRef: 'block'})).toEqual( + 'hm://d/123#block', + ) + }) + test('creates hm://a/123?v=foo#bar', () => { + expect(createHmId('a', '123', {version: 'foo', blockRef: 'bar'})).toEqual( + 'hm://a/123?v=foo#bar', + ) + }) +}) + +describe('unpackDocId', () => { + it('should return values from matching URLs', () => { + const result = unpackDocId('https://hyper.media/d/foo?v=bar#block') + expect(result).toEqual({ + docId: 'hm://d/foo', + eid: 'foo', + hostname: 'hyper.media', + scheme: 'https', + type: 'd', + version: 'bar', + blockRef: 'block', + }) + }) + + it('should handle URLs without version and blockId', () => { + const result = unpackDocId('http://gabo.es/d/anotherpath') + expect(result).toEqual({ + docId: 'hm://d/anotherpath', + eid: 'anotherpath', + hostname: 'gabo.es', + scheme: 'http', + type: 'd', + version: undefined, + blockRef: undefined, + }) + }) + + it('should handle Fully Qualified IDs', () => { + const result = unpackDocId('hm://d/abc123') + expect(result).toEqual({ + docId: 'hm://d/abc123', + eid: 'abc123', + hostname: undefined, + scheme: 'hm', + type: 'd', + version: undefined, + blockRef: undefined, + }) + }) +}) diff --git a/frontend/packages/shared/src/utils/__tests__/get-ids-from-url.test.ts b/frontend/packages/shared/src/utils/__tests__/get-ids-from-url.test.ts deleted file mode 100644 index b643d4dac3..0000000000 --- a/frontend/packages/shared/src/utils/__tests__/get-ids-from-url.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - extractHypermediaWebsiteValues, - getIdsfromUrl, - matchesHypermediaPattern, -} from '../get-ids-from-url' -import {describe, expect, it, test} from 'vitest' - -const testCases: Array<[string, boolean]> = [ - ['https://mintter.com/d/foo', true], - ['https://www.mintter.com/d/foo', true], - ['https://mintter.com/d/foo?v=bar', true], - ['https://mintter.com/d/foo?v=bar#block', true], - ['https://www.mintter.com/d/foo?v=bar#block', true], - ['https://gabo.es/d/anotherpath', true], - ['https://gabo.es/d/anotherpath?v=versionhere', true], - ['https://gabo.es/d/anotherpath?v=bar#block', true], - ['https://www.hhg.link/g/somegroupid', true], - ['https://juligasa.es/a/accountid', true], - ['https://example.com/invalid', false], - ['http://example.com/a/shouldbeinvalid', false], -] - -describe('matchesHypermediaPattern', () => { - test.each(testCases)('should match valid URLs', (url, expected) => { - expect(matchesHypermediaPattern(url)).toBe(expected) - }) -}) - -describe('extractHypermediaWebsiteValues', () => { - it('should extract values from valid URLs', () => { - const values = extractHypermediaWebsiteValues( - 'https://mintter.com/d/foo?v=bar#block', - ) - expect(values).toEqual({ - hostname: 'mintter.com', - pathType: 'd', - docId: 'foo', - version: 'bar', - blockId: 'block', - }) - // Add more test cases here - }) - - it('should return null for invalid URLs', () => { - const values = extractHypermediaWebsiteValues('https://example.com/invalid') - expect(values).toBeNull() - // Add more test cases here - }) -}) - -describe('getIdsfromUrl', () => { - it('should return values from matching URLs', () => { - const result = getIdsfromUrl('https://mintter.com/d/foo?v=bar#block') - expect(result).toEqual(['foo', 'bar', 'block']) - // Add more test cases here - }) - - it('should handle URLs without version and blockId', () => { - const result = getIdsfromUrl('https://gabo.es/d/anotherpath') - expect(result).toEqual(['anotherpath', undefined, undefined]) - // Add more test cases here - }) - - it('should handle Fully Qualified IDs', () => { - const result = getIdsfromUrl('hm://d/abc123') - expect(result).toEqual(['abc123', undefined, undefined]) - // Add more test cases here - }) - - it('should handle invalid entries', () => { - const result = getIdsfromUrl('https://example.com/invalid') - expect(result).toEqual([undefined, undefined, undefined]) - // Add more test cases here - }) -}) diff --git a/frontend/packages/shared/src/utils/doc-url.ts b/frontend/packages/shared/src/utils/doc-url.ts deleted file mode 100644 index 04d9f4ee42..0000000000 --- a/frontend/packages/shared/src/utils/doc-url.ts +++ /dev/null @@ -1,60 +0,0 @@ -import {Publication, WebPublicationRecord} from '../client' - -const publicWebHost = 'https://mintter.com' - -export function getDocUrl( - pub: Publication | undefined, - webPub?: WebPublicationRecord, -): string | null { - const id = pub?.document?.id - if (!id) { - return null - } - const publishedWebHost = pub?.document - ? pub.document.webUrl || publicWebHost - : null - let path = `/d/${id}` - if (webPub?.path === '/') { - path = '/' - } else if (webPub?.path) { - path = `/${webPub.path}` - } - let docUrl = `${publishedWebHost}${path}?v=${pub.version}` - - return docUrl -} - -export function getPublicDocUrl(docId: string, version?: string | undefined) { - let webUrl = `${publicWebHost}/d/${docId}` - if (version) return `${webUrl}?v=${version}` - return webUrl -} - -export function extractEntityId(id: string): [string, string] | null { - // input is like hm://x/abcd. output is ['x', 'abcd'] - const m = id.match(/^hm:\/\/([^/]+)\/(.+)$/) - if (!m) return null - const entityType = m[1] - const entityEId = m[2] - return [entityType, entityEId] -} - -export function entityIdToSitePath(entityId?: string): string | null { - const [entityType, entityEId] = extractEntityId(entityId || '') || [] - if (!entityType || !entityEId) return null - if (entityType === 'g') return `/g/${entityEId}` - if (entityType === 'a') return `/a/${entityEId}` - if (entityType === 'd') return `/d/${entityEId}` - return null -} - -export function getPublicEntityUrl( - groupId: string, - version?: string | undefined, -) { - const extractedId = extractEntityId(groupId) - if (!extractedId) return null - let webUrl = `${publicWebHost}/${extractedId?.[0]}/${extractedId?.[1]}` - if (version) return `${webUrl}?v=${version}` - return webUrl -} diff --git a/frontend/packages/shared/src/utils/entity-id-url.ts b/frontend/packages/shared/src/utils/entity-id-url.ts new file mode 100644 index 0000000000..bf09c89ad9 --- /dev/null +++ b/frontend/packages/shared/src/utils/entity-id-url.ts @@ -0,0 +1,195 @@ +export const HYPERMEDIA_PUBLIC_WEB_GATEWAY = 'https://hyper.media' + +export const HYPERMEDIA_SCHEME = 'hm' +export const HYPERMEDIA_ENTITY_TYPES = { + a: 'Account', + d: 'Document', + g: 'Group', +} as const + +export function getPublicDocUrl(docId: string, version?: string | undefined) { + let webUrl = `${HYPERMEDIA_PUBLIC_WEB_GATEWAY}/d/${docId}` + if (version) return `${webUrl}?v=${version}` + return webUrl +} + +export function extractEntityId(id: string): [string, string] | null { + // input is like hm://x/abcd. output is ['x', 'abcd'] + const m = id.match(/^hm:\/\/([^/]+)\/(.+)$/) + if (!m) return null + const entityType = m[1] + const entityEId = m[2] + return [entityType, entityEId] +} + +export function isValidSiteEntity(entityType: string) { + if (entityType === 'a') return true + if (entityType === 'd') return true + if (entityType === 'g') return true + return false +} + +export function createPublicWebHmUrl( + type: keyof typeof HYPERMEDIA_ENTITY_TYPES, + eid: string, + { + version, + blockRef, + hostname, + }: { + version?: string + blockRef?: string + hostname?: string | null | undefined + } = {}, +) { + const webPath = `/${type}/${eid}` + let urlHost = + hostname === null + ? '' + : hostname === undefined + ? HYPERMEDIA_PUBLIC_WEB_GATEWAY + : hostname + let webUrl = `${urlHost}${webPath}` + if (version) webUrl += `?v=${version}` + if (blockRef) webUrl += `#${blockRef}` + return webUrl +} + +export function createHmId( + type: keyof typeof HYPERMEDIA_ENTITY_TYPES, + id: string, + opts?: {version?: string; blockRef?: string}, +) { + let outputUrl = `${HYPERMEDIA_SCHEME}://${type}/${id}` + if (opts?.version) outputUrl += `?v=${opts.version}` + if (opts?.blockRef) outputUrl += `#${opts.blockRef}` + return outputUrl +} + +type ParsedURL = { + scheme: string | null + path: string[] + query: Record + fragment: string | null +} + +export function parseCustomURL(url: string): ParsedURL | null { + const [scheme, rest] = url.split('://') + if (!rest) return null + const [pathAndQuery, fragment] = rest.split('#') + const [path, queryString] = pathAndQuery.split('?') + + const query: Record = {} + queryString?.split('&').forEach((param) => { + const [key, value] = param.split('=') + query[key] = decodeURIComponent(value) + }) + + return { + scheme, + path: path.split('/'), + query, + fragment: fragment || null, + } +} + +function inKeys( + key: string, + values: Record, +): V | null { + // @ts-expect-error + if (values[key]) return key as V + return null +} + +export function unpackHmId(hypermediaId: string) { + const parsed = parseCustomURL(hypermediaId) + if (parsed?.scheme === HYPERMEDIA_SCHEME) { + const type = inKeys(parsed?.path[0], HYPERMEDIA_ENTITY_TYPES) + const eid = parsed?.path[1] + const version = parsed?.query.v + return { + type, + eid, + version, + blockRef: parsed?.fragment || undefined, + hostname: undefined, + scheme: parsed?.scheme, + } + } + if (parsed?.scheme === 'https' || parsed?.scheme === 'http') { + const type = inKeys(parsed?.path[1], HYPERMEDIA_ENTITY_TYPES) + const eid = parsed?.path[2] + const version = parsed?.query.v + let hostname = parsed?.path[0] + return { + type, + eid, + version, + blockRef: parsed?.fragment || undefined, + hostname, + scheme: parsed?.scheme, + } + } + return null +} + +export function unpackDocId(inputUrl: string) { + const unpackedHm = unpackHmId(inputUrl) + if (!unpackedHm?.eid) return null + if (unpackedHm.type !== 'd') { + throw new Error('URL is expected to be a document ID: ' + inputUrl) + } + return { + eid: unpackedHm.eid, + type: 'd', + docId: createHmId('d', unpackedHm.eid), + version: unpackedHm.version, + blockRef: unpackedHm.blockRef, + hostname: unpackedHm.hostname, + scheme: unpackedHm.scheme, + } +} + +export function isHypermediaScheme(url?: string) { + return !!url?.startsWith(`${HYPERMEDIA_SCHEME}://`) +} + +export function isPublicGatewayLink(text: string) { + const matchesGateway = text.indexOf(HYPERMEDIA_PUBLIC_WEB_GATEWAY) === 0 + console.log('PATH', text.split(HYPERMEDIA_PUBLIC_WEB_GATEWAY)[1]) + return !!matchesGateway +} + +export function idToUrl(hmId: string, hostname?: string | null) { + const unpacked = unpackHmId(hmId) + if (!unpacked?.type) return null + return createPublicWebHmUrl(unpacked.type, unpacked.eid, { + version: unpacked.version, + blockRef: unpacked.blockRef, + hostname, + }) +} + +export function normlizeHmId(urlMaybe: string): string | undefined { + if (isHypermediaScheme(urlMaybe)) return urlMaybe + if (isPublicGatewayLink(urlMaybe)) { + const unpacked = unpackHmId(urlMaybe) + + if (unpacked?.eid && unpacked.type) { + return createHmId(unpacked.type, unpacked.eid, unpacked) + } + return undefined + } +} + +export function createHmDocLink( + documentId: string, + version?: string | null, + blockRef?: string | null, +): string { + let res = documentId + if (version) res += `?v=${version}` + if (blockRef) res += `${!blockRef.startsWith('#') ? '#' : ''}${blockRef}` + return res +} diff --git a/frontend/packages/shared/src/utils/entity-ids.ts b/frontend/packages/shared/src/utils/entity-ids.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frontend/packages/shared/src/utils/get-ids-from-url.ts b/frontend/packages/shared/src/utils/get-ids-from-url.ts deleted file mode 100644 index 2dae3a5633..0000000000 --- a/frontend/packages/shared/src/utils/get-ids-from-url.ts +++ /dev/null @@ -1,88 +0,0 @@ -export const HYPERMEDIA_DOCUMENT_PREFIX = 'hm://d/' -export const HYPERMEDIA_ACCOUNT_PREFIX = 'hm://a/' -export const HYPERMEDIA_GROUP_PREFIX = 'hm://g/' -export const HYPERMEDIA_SITES_PATTERN = - /^https:\/\/(www\.)?([^/]+)\/(d|a|g)\/([^/?#]+)(\?.+)?(#.+)?$/ - -export function matchesHypermediaPattern(url: string): boolean { - return HYPERMEDIA_SITES_PATTERN.test(url) - - // Test cases - // const testCases = [ - // "https://mintter.com/d/foo", - // "https://mintter.com/d/foo?v=bar", - // "https://mintter.com/d/foo?v=bar#block", - // "https://www.mintter.com/d/foo?v=bar#block", - // "https://gabo.es/d/anotherpath", - // "https://gabo.es/d/anotherpath?v=versionhere", - // "https://gabo.es/d/anotherpath?v=bar#block", - // "https://www.hhg.link/g/somegroupid", - // "https://juligasa.es/a/accountid" - // ]; -} - -export function extractHypermediaWebsiteValues(url: string) { - const match = url.match(HYPERMEDIA_SITES_PATTERN) - - if (!match) { - return null - } - - const [, , hostname, pathType, docId, queryAndFragment] = match - const [query, fragment] = queryAndFragment ? queryAndFragment.split('#') : [] - - const versionMatch = query && query.match(/[?&]v=([^&]+)/) - const version = versionMatch ? versionMatch[1] : undefined - - return { - hostname, - pathType, - docId, - version, - blockId: fragment || undefined, - } -} - -export function getIdsfromUrl( - entry: string, -): [docId: string | undefined, version?: string, blockId?: string] { - console.log(`[getIdsfromUrl]: START -> ${entry}`) - if (matchesHypermediaPattern(entry)) { - const values = extractHypermediaWebsiteValues(entry) - if (values) { - let {docId, version, blockId} = values - - console.log( - `[getIdsfromUrl]: entry match web sites pattern: ${entry} -> ${JSON.stringify( - values, - )}`, - ) - return [docId, version, blockId] - } - } - - if (entry.startsWith(HYPERMEDIA_DOCUMENT_PREFIX)) { - const [, restUrl] = entry.split(HYPERMEDIA_DOCUMENT_PREFIX) - - if (restUrl.length > 3) { - let firstSplit = restUrl.split('?v=') - const [docId] = firstSplit - const [version, blockId] = - firstSplit.length > 1 - ? restUrl.split('?v=')[1].split('#') - : [undefined, undefined] - console.log( - `[getIdsfromUrl]: entry match Fully Qualified ID: ${entry}, docId = ${docId}, version = ${version}, blockId = ${blockId}`, - ) - return [docId, version, blockId] - } else { - console.warn( - `[getIdsfromUrl]: entry match Fully Qualified ID, but does not satisfy the correct length: ${entry}`, - ) - } - } - - console.warn(`[getIdsfromUrl]: entry does not match any pattern: ${entry}`) - - return [undefined, undefined, undefined] -} diff --git a/frontend/packages/shared/src/utils/hyperdocs-link.ts b/frontend/packages/shared/src/utils/hyperdocs-link.ts deleted file mode 100644 index 0d379c9fef..0000000000 --- a/frontend/packages/shared/src/utils/hyperdocs-link.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { - extractHypermediaWebsiteValues, - getIdsfromUrl, - HYPERMEDIA_DOCUMENT_PREFIX, -} from './get-ids-from-url' - -export function isHypermediaScheme(url?: string) { - return !!url?.startsWith(HYPERMEDIA_DOCUMENT_PREFIX) -} - -export function isMintterGatewayLink(text: string) { - return extractHypermediaWebsiteValues(text) -} - -export function normalizeHypermediaLink(urlMaybe: string): string | undefined { - if (isHypermediaScheme(urlMaybe)) return urlMaybe - if (isMintterGatewayLink(urlMaybe)) { - const [docId, version, blockRef] = getIdsfromUrl(urlMaybe) - - if (docId && docId != 'undefined') { - return createHyperdocsDocLink(docId, version, blockRef) - } - return undefined - } -} - -export function createLinkParams( - documentId: string, - version?: string | null, - blockRef?: string | null, -): string { - let res = documentId - if (version) res += `?v=${version}` - if (blockRef) res += `${!blockRef.startsWith('#') ? '#' : ''}${blockRef}` - - return res -} - -export function createHyperdocsDocLink( - documentId: string, - version?: string | null, - blockRef?: string | null, -): string { - return `${HYPERMEDIA_DOCUMENT_PREFIX}${createLinkParams( - documentId, - version, - blockRef, - )}` -} - -export function createMintterLink( - documentId: string, - version?: string | null, - blockRef?: string | null, -): string { - return `https://mintter.com/d/${createLinkParams( - documentId, - version, - blockRef, - )}` -} diff --git a/frontend/packages/shared/src/utils/index.ts b/frontend/packages/shared/src/utils/index.ts index 3d81b82e8e..375a83354f 100644 --- a/frontend/packages/shared/src/utils/index.ts +++ b/frontend/packages/shared/src/utils/index.ts @@ -1,7 +1,5 @@ export * from './abbreviate' export * from './date' -export * from './doc-url' -export * from './get-ids-from-url' +export * from './entity-id-url' export * from './language' export * from './ipfs-cid' -export * from './hyperdocs-link' diff --git a/yarn.lock b/yarn.lock index 7f3e57c0e7..afc96a6a66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6905,15 +6905,15 @@ __metadata: languageName: node linkType: hard -"@sentry-internal/tracing@npm:7.68.0, @sentry-internal/tracing@npm:^7.66.0": - version: 7.68.0 - resolution: "@sentry-internal/tracing@npm:7.68.0" +"@sentry-internal/tracing@npm:7.69.0, @sentry-internal/tracing@npm:^7.66.0": + version: 7.69.0 + resolution: "@sentry-internal/tracing@npm:7.69.0" dependencies: - "@sentry/core": 7.68.0 - "@sentry/types": 7.68.0 - "@sentry/utils": 7.68.0 + "@sentry/core": 7.69.0 + "@sentry/types": 7.69.0 + "@sentry/utils": 7.69.0 tslib: ^2.4.1 || ^1.9.3 - checksum: 93523660ede29988054451e6af6e4f38102753a0bd851c0623ccd05207e3fc63eaf01559c1ffc76e4e234624df9ee26834e693426995907ce646cb750e80a3f2 + checksum: 3ccb7e7d008dd39ed2bb9a02fcd7ae6161a8355451891db25020d8068357254a430e697c4f72c4d1d747754585ca0f610cea6798d51b6a791ae2c73ee399b58e languageName: node linkType: hard @@ -6931,17 +6931,17 @@ __metadata: languageName: node linkType: hard -"@sentry/browser@npm:7.68.0": - version: 7.68.0 - resolution: "@sentry/browser@npm:7.68.0" +"@sentry/browser@npm:7.69.0": + version: 7.69.0 + resolution: "@sentry/browser@npm:7.69.0" dependencies: - "@sentry-internal/tracing": 7.68.0 - "@sentry/core": 7.68.0 - "@sentry/replay": 7.68.0 - "@sentry/types": 7.68.0 - "@sentry/utils": 7.68.0 + "@sentry-internal/tracing": 7.69.0 + "@sentry/core": 7.69.0 + "@sentry/replay": 7.69.0 + "@sentry/types": 7.69.0 + "@sentry/utils": 7.69.0 tslib: ^2.4.1 || ^1.9.3 - checksum: 85d688902057d85113cd6fb46e4ca17167bfda6ce77c8264f1f29bf7d451dd73315ebcf7134ac91d539fc4032449ac25b0dd6e9209695bd934a4716acda55aac + checksum: 8b5460c1733628ecb1dee16228f0e055fcf7787f97ce85554a5fddd7dbdf787a6be0f61c8180756144139ec3bb6a0647e95f82772e601353f1fc64e32d5fda91 languageName: node linkType: hard @@ -7014,14 +7014,14 @@ __metadata: languageName: node linkType: hard -"@sentry/core@npm:7.68.0": - version: 7.68.0 - resolution: "@sentry/core@npm:7.68.0" +"@sentry/core@npm:7.69.0": + version: 7.69.0 + resolution: "@sentry/core@npm:7.69.0" dependencies: - "@sentry/types": 7.68.0 - "@sentry/utils": 7.68.0 + "@sentry/types": 7.69.0 + "@sentry/utils": 7.69.0 tslib: ^2.4.1 || ^1.9.3 - checksum: 1601fba3f7c8b2f35c3212d8f51690cb25efb17a7c21698ae67fdcbe3ae17f56c381edb51870739649752aee291efe742fb3e169b3118dc2690eb820de043766 + checksum: b24ec3121dd899dc53edaf1ca984f6df4fab3cd9dc1756b2037729c61e33df47ad4b94abb0dc24fc2dfb6099396a3e9df7f13d0e4673184c93e5932dcfb9a8e1 languageName: node linkType: hard @@ -7040,29 +7040,29 @@ __metadata: languageName: node linkType: hard -"@sentry/integrations@npm:7.68.0": - version: 7.68.0 - resolution: "@sentry/integrations@npm:7.68.0" +"@sentry/integrations@npm:7.69.0": + version: 7.69.0 + resolution: "@sentry/integrations@npm:7.69.0" dependencies: - "@sentry/types": 7.68.0 - "@sentry/utils": 7.68.0 + "@sentry/types": 7.69.0 + "@sentry/utils": 7.69.0 localforage: ^1.8.1 tslib: ^2.4.1 || ^1.9.3 - checksum: 7bc15750c93015afc661bf3306628a3d34b16e34a5f56758ac8ec25ee33a80d0191f28ee40e3fe821408f4c725c67da32fa1f51ca69f6456c52a1a5e6f54163a + checksum: 500d9e6a0a65f7c5df5f043f476ccda91d15b223fe4119196dfa6f9d004fd53f62951187308d3e50c185e1f908f4024547e1889f71c886869aac075e7e8128cb languageName: node linkType: hard "@sentry/nextjs@npm:latest": - version: 7.68.0 - resolution: "@sentry/nextjs@npm:7.68.0" + version: 7.69.0 + resolution: "@sentry/nextjs@npm:7.69.0" dependencies: "@rollup/plugin-commonjs": 24.0.0 - "@sentry/core": 7.68.0 - "@sentry/integrations": 7.68.0 - "@sentry/node": 7.68.0 - "@sentry/react": 7.68.0 - "@sentry/types": 7.68.0 - "@sentry/utils": 7.68.0 + "@sentry/core": 7.69.0 + "@sentry/integrations": 7.69.0 + "@sentry/node": 7.69.0 + "@sentry/react": 7.69.0 + "@sentry/types": 7.69.0 + "@sentry/utils": 7.69.0 "@sentry/webpack-plugin": 1.20.0 chalk: 3.0.0 rollup: 2.78.0 @@ -7075,7 +7075,7 @@ __metadata: peerDependenciesMeta: webpack: optional: true - checksum: afe7b745484fdd937f3969c2635f1aa5df20d63b5d35f2a9e365870871f4b9ac89e2a9520bbb76da8538be8ae059a46ecb8f61a21292a47451ff8048dac384f6 + checksum: c2b8580eaa6c3bdb48c56555c6afdbf8cae755953dcf4a41dd3a6f9c5f6b5769cb2f648e9eccc0e97373db6fc0fa31b28639dd1e63954a54647088e76fb9fc11 languageName: node linkType: hard @@ -7095,34 +7095,34 @@ __metadata: languageName: node linkType: hard -"@sentry/node@npm:7.68.0, @sentry/node@npm:^7.60.0": - version: 7.68.0 - resolution: "@sentry/node@npm:7.68.0" +"@sentry/node@npm:7.69.0, @sentry/node@npm:^7.60.0": + version: 7.69.0 + resolution: "@sentry/node@npm:7.69.0" dependencies: - "@sentry-internal/tracing": 7.68.0 - "@sentry/core": 7.68.0 - "@sentry/types": 7.68.0 - "@sentry/utils": 7.68.0 + "@sentry-internal/tracing": 7.69.0 + "@sentry/core": 7.69.0 + "@sentry/types": 7.69.0 + "@sentry/utils": 7.69.0 cookie: ^0.4.1 https-proxy-agent: ^5.0.0 lru_map: ^0.3.3 tslib: ^2.4.1 || ^1.9.3 - checksum: bcbcce099830e89a8b0cd48b4505fa4f88d14d818cdb3a6861dba8cd0e77b492733fb1e1f33f6aa5606b05b14146a23dffdb4b829a85bb3c31b2bc7b7b5290fb + checksum: 97210ced968a3d968fd9d93e67e1f3c9613b99b223f87fad944e6e94db40ebc10a7c339c848e0529c5ded69f94f1f689b4a6df1da4df1aad6663a752ac591d03 languageName: node linkType: hard -"@sentry/react@npm:7.68.0": - version: 7.68.0 - resolution: "@sentry/react@npm:7.68.0" +"@sentry/react@npm:7.69.0": + version: 7.69.0 + resolution: "@sentry/react@npm:7.69.0" dependencies: - "@sentry/browser": 7.68.0 - "@sentry/types": 7.68.0 - "@sentry/utils": 7.68.0 + "@sentry/browser": 7.69.0 + "@sentry/types": 7.69.0 + "@sentry/utils": 7.69.0 hoist-non-react-statics: ^3.3.2 tslib: ^2.4.1 || ^1.9.3 peerDependencies: react: 15.x || 16.x || 17.x || 18.x - checksum: 73b9c3e2e3805ec068d95ceef0702783604c972b1ea873523ec63af9b3ce73667eb9fecbfe0838a5b0f33fe87da97cd2f6c1a74859e342c637adf15999f1bae2 + checksum: 5aa863918be7e7122967c40e0f83946081eda2fc05ff563d0a65ba0329e51954992576e6e5b27d714b134a603c06067cc9ff78fb8f8c8c43824a3ebe02c127ae languageName: node linkType: hard @@ -7137,14 +7137,14 @@ __metadata: languageName: node linkType: hard -"@sentry/replay@npm:7.68.0": - version: 7.68.0 - resolution: "@sentry/replay@npm:7.68.0" +"@sentry/replay@npm:7.69.0": + version: 7.69.0 + resolution: "@sentry/replay@npm:7.69.0" dependencies: - "@sentry/core": 7.68.0 - "@sentry/types": 7.68.0 - "@sentry/utils": 7.68.0 - checksum: a54871ec011bdd7df329fe684f3790932d1bd3c08ea7a849f5e1d9bde6cdae3b96993687522a92e0cfaf529f4e7439e28db1da5216e13d633af2dcf121b6f505 + "@sentry/core": 7.69.0 + "@sentry/types": 7.69.0 + "@sentry/utils": 7.69.0 + checksum: 47d9ccfd6a619ef1640a2649fccce7a0824e52e12c84759d40c569416ad65886e50bd30c3b789c6fe9d121060e3e55f4344925470dfd9db12ac678703b005f72 languageName: node linkType: hard @@ -7171,10 +7171,10 @@ __metadata: languageName: node linkType: hard -"@sentry/types@npm:7.68.0": - version: 7.68.0 - resolution: "@sentry/types@npm:7.68.0" - checksum: 9854e14b6864dc2b649655dc10a6f05d1e960d8fb2e2b0cb178073d884118314f790aca633d208ab0ab76b173523a66d066fb915294c0d5412e00456fa89343d +"@sentry/types@npm:7.69.0": + version: 7.69.0 + resolution: "@sentry/types@npm:7.69.0" + checksum: aaa40a43cab358e10c2566d62966eff61925fb2605c146967bf9eb8acb4a883d4ca7c8a5eee1915271da08f27ddf1ed7dc520a8617f229ce70c7d00557173cc4 languageName: node linkType: hard @@ -7198,13 +7198,13 @@ __metadata: languageName: node linkType: hard -"@sentry/utils@npm:7.68.0, @sentry/utils@npm:^7.60.0": - version: 7.68.0 - resolution: "@sentry/utils@npm:7.68.0" +"@sentry/utils@npm:7.69.0, @sentry/utils@npm:^7.60.0": + version: 7.69.0 + resolution: "@sentry/utils@npm:7.69.0" dependencies: - "@sentry/types": 7.68.0 + "@sentry/types": 7.69.0 tslib: ^2.4.1 || ^1.9.3 - checksum: fd80eb9379ffb208864906b693e599d01a9229f523cf6516c261abe37316afd9dedd83a5690355e4823290164099e129957bf7dc3891ab724ac6e00b56908cc9 + checksum: 8c18b6a1c5a869d608ff9cdb4534b29c5c85a06660647d8d8dfd67460be985d44a88f1ec4335174d40f147fecaa2e952b78747e83b6ed2f654e93248744fa291 languageName: node linkType: hard @@ -10892,13 +10892,13 @@ __metadata: linkType: hard "algoliasearch-helper@npm:^3.10.0": - version: 3.14.0 - resolution: "algoliasearch-helper@npm:3.14.0" + version: 3.14.1 + resolution: "algoliasearch-helper@npm:3.14.1" dependencies: "@algolia/events": ^4.0.1 peerDependencies: algoliasearch: ">= 3.1 < 6" - checksum: bfda2125432eb6807241bb1ebfeb9b43ece456a759e160c28f34ef50c53eb763428fe7276a9e6b32ae70eba7346f89a1251524b5c8cfebe3bce45abe44af669d + checksum: 30b0ebba46450cb44ec1625b1425d9c4f7bf0660d6d5dd0448f98d690418bd709c19ee6c1631293333922cdcc22cd5621d0bf48407dbd04273810c61affdc2f2 languageName: node linkType: hard @@ -12368,9 +12368,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001406, caniuse-lite@npm:^1.0.30001517, caniuse-lite@npm:^1.0.30001520": - version: 1.0.30001533 - resolution: "caniuse-lite@npm:1.0.30001533" - checksum: 1ffb2d69f817ee5f13351cb01c26a98ac61515809a6ce355305df3fbc6de6391a58466c1dcad1f322231b1ddc59bdda9bc52b9480cac100c3ff2f5782e859eb1 + version: 1.0.30001534 + resolution: "caniuse-lite@npm:1.0.30001534" + checksum: 8e8b63c1ce0d5b944ee2d8223955b33f3d4f60c33fed394ff6353b5c7106b2dc55219fd07d80c79e66ed1f82ed9367cee17bda96789cbd2ab57c8d30b1b5c510 languageName: node linkType: hard @@ -12754,9 +12754,9 @@ __metadata: linkType: hard "cli-spinners@npm:^2.5.0": - version: 2.9.0 - resolution: "cli-spinners@npm:2.9.0" - checksum: a9c56e1f44457d4a9f4f535364e729cb8726198efa9e98990cfd9eda9e220dfa4ba12f92808d1be5e29029cdfead781db82dc8549b97b31c907d55f96aa9b0e2 + version: 2.9.1 + resolution: "cli-spinners@npm:2.9.1" + checksum: 1780618be58309c469205bc315db697934bac68bce78cd5dfd46248e507a533172d623c7348ecfd904734f597ce0a4e5538684843d2cfb7af485d4466699940c languageName: node linkType: hard @@ -14186,6 +14186,17 @@ __metadata: languageName: node linkType: hard +"define-data-property@npm:^1.0.1": + version: 1.1.0 + resolution: "define-data-property@npm:1.1.0" + dependencies: + get-intrinsic: ^1.2.1 + gopd: ^1.0.1 + has-property-descriptors: ^1.0.0 + checksum: 7ad4ee84cca8ad427a4831f5693526804b62ce9dfd4efac77214e95a4382aed930072251d4075dc8dc9fc949a353ed51f19f5285a84a788ba9216cc51472a093 + languageName: node + linkType: hard + "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -14201,12 +14212,13 @@ __metadata: linkType: hard "define-properties@npm:^1.1.3, define-properties@npm:^1.1.4, define-properties@npm:^1.2.0": - version: 1.2.0 - resolution: "define-properties@npm:1.2.0" + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" dependencies: + define-data-property: ^1.0.1 has-property-descriptors: ^1.0.0 object-keys: ^1.1.1 - checksum: e60aee6a19b102df4e2b1f301816804e81ab48bb91f00d0d935f269bf4b3f79c88b39e4f89eaa132890d23267335fd1140dfcd8d5ccd61031a0a2c41a54e33a6 + checksum: b4ccd00597dd46cb2d4a379398f5b19fca84a16f3374e2249201992f36b30f6835949a9429669ee6b41b6e837205a163eadd745e472069e70dfc10f03e5fcc12 languageName: node linkType: hard @@ -14839,9 +14851,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.4.477": - version: 1.4.517 - resolution: "electron-to-chromium@npm:1.4.517" - checksum: e6a6643bf9cd4fe60d0ab902167e5b04bf183a6abbc246d7e4125ef4f76e3b3ff9847dcd34caaf545d9c6ee13ca9ef87ebe29fa11f64f6a0b369b40ed435dec8 + version: 1.4.519 + resolution: "electron-to-chromium@npm:1.4.519" + checksum: 2599e5b62a045698015f5feafa0ca19d659d887a9b289cde19c24981d06ede843feb928c7ef1bb2aa63a1d25694d1b1e385c9c0959d56b4bfe98b05cb63eecf0 languageName: node linkType: hard @@ -20616,14 +20628,14 @@ __metadata: linkType: hard "lib0@npm:^0.2.42, lib0@npm:^0.2.74": - version: 0.2.84 - resolution: "lib0@npm:0.2.84" + version: 0.2.85 + resolution: "lib0@npm:0.2.85" dependencies: isomorphic.js: ^0.2.4 bin: 0gentesthtml: bin/gentesthtml.js 0serve: bin/0serve.js - checksum: 4403435191dec226e3c61aac994cd1f4dbb0c4988e2f600fb5a398d2f99ea85dc060db9756237d9084a936ffdbec141fc13e8e8e1c53db795fb9f7e7602b9c38 + checksum: 6a3c5c5c3f37f0940eff9309b2595f9a77822f521773db773e0d809309ccf5e6ecab8f39cc47b55b6b167f60b3824c44bb7d92b5c9ffb81a3f331b21277906d2 languageName: node linkType: hard @@ -26453,13 +26465,13 @@ __metadata: linkType: hard "regexp.prototype.flags@npm:^1.5.0": - version: 1.5.0 - resolution: "regexp.prototype.flags@npm:1.5.0" + version: 1.5.1 + resolution: "regexp.prototype.flags@npm:1.5.1" dependencies: call-bind: ^1.0.2 define-properties: ^1.2.0 - functions-have-names: ^1.2.3 - checksum: c541687cdbdfff1b9a07f6e44879f82c66bbf07665f9a7544c5fd16acdb3ec8d1436caab01662d2fbcad403f3499d49ab0b77fbc7ef29ef961d98cc4bc9755b4 + set-function-name: ^2.0.0 + checksum: 869edff00288442f8d7fa4c9327f91d85f3b3acf8cbbef9ea7a220345cf23e9241b6def9263d2c1ebcf3a316b0aa52ad26a43a84aa02baca3381717b3e307f47 languageName: node linkType: hard @@ -27610,6 +27622,17 @@ __metadata: languageName: node linkType: hard +"set-function-name@npm:^2.0.0": + version: 2.0.1 + resolution: "set-function-name@npm:2.0.1" + dependencies: + define-data-property: ^1.0.1 + functions-have-names: ^1.2.3 + has-property-descriptors: ^1.0.0 + checksum: 4975d17d90c40168eee2c7c9c59d023429f0a1690a89d75656306481ece0c3c1fb1ebcc0150ea546d1913e35fbd037bace91372c69e543e51fc5d1f31a9fa126 + languageName: node + linkType: hard + "set-value@npm:^2.0.0, set-value@npm:^2.0.1": version: 2.0.1 resolution: "set-value@npm:2.0.1" @@ -28396,8 +28419,8 @@ __metadata: linkType: hard "string.prototype.matchall@npm:^4.0.8": - version: 4.0.9 - resolution: "string.prototype.matchall@npm:4.0.9" + version: 4.0.10 + resolution: "string.prototype.matchall@npm:4.0.10" dependencies: call-bind: ^1.0.2 define-properties: ^1.2.0 @@ -28406,8 +28429,9 @@ __metadata: has-symbols: ^1.0.3 internal-slot: ^1.0.5 regexp.prototype.flags: ^1.5.0 + set-function-name: ^2.0.0 side-channel: ^1.0.4 - checksum: a68a9914755ec8c9b9129d6fb70603d78b839a1ca4a907e601fcb860109cecfbd1884e8b38989885f9c825c1c2015ff5b1ed9ddac7c8d040e4e4b3c9bc4ed5ed + checksum: 3c78bdeff39360c8e435d7c4c6ea19f454aa7a63eda95fa6fadc3a5b984446a2f9f2c02d5c94171ce22268a573524263fbd0c8edbe3ce2e9890d7cc036cdc3ed languageName: node linkType: hard @@ -29722,21 +29746,21 @@ __metadata: "typescript@patch:typescript@5.1.6#~builtin": version: 5.1.6 - resolution: "typescript@patch:typescript@npm%3A5.1.6#~builtin::version=5.1.6&hash=5da071" + resolution: "typescript@patch:typescript@npm%3A5.1.6#~builtin::version=5.1.6&hash=77c9e2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: f53bfe97f7c8b2b6d23cf572750d4e7d1e0c5fff1c36d859d0ec84556a827b8785077bc27676bf7e71fae538e517c3ecc0f37e7f593be913d884805d931bc8be + checksum: 21e88b0a0c0226f9cb9fd25b9626fb05b4c0f3fddac521844a13e1f30beb8f14e90bd409a9ac43c812c5946d714d6e0dee12d5d02dfc1c562c5aacfa1f49b606 languageName: node linkType: hard "typescript@patch:typescript@^5.2.2#~builtin": version: 5.2.2 - resolution: "typescript@patch:typescript@npm%3A5.2.2#~builtin::version=5.2.2&hash=f3b441" + resolution: "typescript@patch:typescript@npm%3A5.2.2#~builtin::version=5.2.2&hash=77c9e2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 0f4da2f15e6f1245e49db15801dbee52f2bbfb267e1c39225afdab5afee1a72839cd86000e65ee9d7e4dfaff12239d28beaf5ee431357fcced15fb08583d72ca + checksum: 07106822b4305de3f22835cbba949a2b35451cad50888759b6818421290ff95d522b38ef7919e70fb381c5fe9c1c643d7dea22c8b31652a717ddbd57b7f4d554 languageName: node linkType: hard From ac1214db3da14140ca02e768ec278333ee69adb6 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Wed, 13 Sep 2023 14:40:51 -0700 Subject: [PATCH 3/4] Path renaming for drafts --- .../app/src/components/titlebar/common.tsx | 2 +- .../src/components/titlebar/publish-share.tsx | 225 ++++++++++++------ frontend/packages/app/src/models/documents.ts | 45 +++- 3 files changed, 181 insertions(+), 91 deletions(-) diff --git a/frontend/packages/app/src/components/titlebar/common.tsx b/frontend/packages/app/src/components/titlebar/common.tsx index fd85a74c86..b85b0c2780 100644 --- a/frontend/packages/app/src/components/titlebar/common.tsx +++ b/frontend/packages/app/src/components/titlebar/common.tsx @@ -309,7 +309,7 @@ export function AccountDropdownItem({ url={getAvatarUrl(account?.profile?.avatar)} /> } - title={account?.profile?.alias || ''} + title={account?.profile?.alias || 'My Profile'} /> ) } diff --git a/frontend/packages/app/src/components/titlebar/publish-share.tsx b/frontend/packages/app/src/components/titlebar/publish-share.tsx index 7659e63abd..819066390e 100644 --- a/frontend/packages/app/src/components/titlebar/publish-share.tsx +++ b/frontend/packages/app/src/components/titlebar/publish-share.tsx @@ -1,5 +1,6 @@ import {useMyAccount} from '@mintter/app/models/accounts' import { + getDefaultShortname, useDraftTitle, usePublication, usePublishDraft, @@ -30,6 +31,7 @@ import { ButtonText, Check, Dialog, + DialogDescription, DialogProps, DialogTitle, Fieldset, @@ -64,6 +66,63 @@ import {useAppDialog} from '../dialog' import {RenamePubDialog} from '@mintter/app/pages/group' import {usePublicationInContext} from '@mintter/app/models/publication' +export function RenameShortnameDialog({ + input: {groupId, pathName, docTitle, draftId}, + onClose, +}: { + input: {groupId: string; pathName: string; docTitle: string; draftId: string} + onClose: () => void +}) { + const [renamed, setRenamed] = useState( + pathName || getDefaultShortname(docTitle, draftId), + ) + const replace = useNavigate('replace') + const route = useNavRoute() + const draftRoute = route.key === 'draft' ? route : null + const group = useGroup(groupId) + if (!draftRoute) return null + const groupRouteContext = + draftRoute.pubContext?.key === 'group' ? draftRoute.pubContext : null + if (!groupRouteContext?.groupId) return null + return ( +
{ + onClose() + toast(pathNameify(renamed)) + replace({ + ...draftRoute, + pubContext: { + key: 'group', + groupId: groupRouteContext.groupId, + pathName: renamed, + }, + }) + }} + > + Publishing Short Path + + Draft will publish in the group {group.data?.title} with the + following path name: + + { + setRenamed( + value + .toLocaleLowerCase() + .replace(/\s+/g, '-') + .replace(/[^a-z0-9-_]/g, '') + .replace(/-{2,}/g, '-'), + ) + }} + /> + + + +
+ ) +} + // function DraftPublicationDialog({ // draft, // }: { @@ -332,85 +391,95 @@ function DraftContextButton({route}: {route: DraftRoute}) { const [isListingGroups, setIsListingGroups] = useState(false) let displayPathName = route.pubContext?.key === 'group' ? route.pubContext?.pathName : undefined - if (!displayPathName && draftTitle) { - displayPathName = pathNameify(draftTitle) + if (!displayPathName) { + displayPathName = getDefaultShortname(draftTitle, route.draftId) } + const shortRename = useAppDialog(RenameShortnameDialog) return ( - - - - - - - - {route.pubContext?.key === 'group' ? ( - <> - Committing to Group: - {}} - route={route} - /> - - ) : null} - - - - {isListingGroups && - (groups.data?.items?.length === 0 ? ( - You have no groups yet - ) : ( - groups.data?.items?.map((item) => { - const groupId = item.group?.id - if (!groupId) return null - const isActive = - groupPubContext?.key === 'group' && - groupId === groupPubContext.groupId - return ( - - ) - }) - ))} - - - + + + + + {groupPubContext ? ( + <> + Committing to Group: + { + shortRename.open({ + groupId: groupPubContext.groupId, + pathName: groupPubContext.pathName, + docTitle: draftTitle, + }) + }} + route={route} + /> + + ) : null} + + + + {isListingGroups && + (groups.data?.items?.length === 0 ? ( + You have no groups yet + ) : ( + groups.data?.items?.map((item) => { + const groupId = item.group?.id + if (!groupId) return null + const isActive = + groupPubContext?.key === 'group' && + groupId === groupPubContext.groupId + return ( + + ) + }) + ))} + + + + {shortRename.content} + ) } @@ -711,13 +780,13 @@ export function DraftPublicationButtons() { let navReplace = useNavigate('replace') const isDaemonReady = useDaemonReady() const publish = usePublishDraft({ - onSuccess: (publishedDoc) => { + onSuccess: ({pub: publishedDoc, pubContext}) => { if (!publishedDoc || !draftId) return navReplace({ key: 'publication', documentId: draftId, versionId: publishedDoc.version, - pubContext: route.pubContext, + pubContext: pubContext, }) toast.success('Document saved and set to public') }, diff --git a/frontend/packages/app/src/models/documents.ts b/frontend/packages/app/src/models/documents.ts index fc2fad28d3..bb413eea38 100644 --- a/frontend/packages/app/src/models/documents.ts +++ b/frontend/packages/app/src/models/documents.ts @@ -34,6 +34,7 @@ import { isHypermediaScheme, isPublicGatewayLink, normlizeHmId, + unpackDocId, } from '@mintter/shared' import {useWidgetViewFactory} from '@prosemirror-adapter/react' import { @@ -255,9 +256,19 @@ function sortDocuments(a?: Timestamp, b?: Timestamp) { return dateB - dateA } +export function getDefaultShortname( + docTitle: string | undefined, + docId: string, +) { + const unpackedId = unpackDocId(docId) + const idShortname = unpackedId ? unpackedId.eid.slice(0, 5).toLowerCase() : '' + const shortname = docTitle ? pathNameify(docTitle) : idShortname + return shortname +} + export function usePublishDraft( opts?: UseMutationOptions< - Publication, + {pub: Publication; pubContext: PublicationRouteContext}, unknown, { draftId: string @@ -280,18 +291,15 @@ export function usePublishDraft( const pub = await grpcClient.drafts.publishDraft({documentId: draftId}) const publishedId = pub.document?.id if (draftGroupContext && publishedId) { - console.log('=== ayyho') let docTitle: string | undefined = ( queryClient.client.getQueryData([ queryKeys.EDITOR_DRAFT, draftId, ]) as any )?.title - console.log('=== ayyho') - let fallbackTitle = docTitle ? docTitle : publishedId.slice(0, 5) - let publishPathName = draftGroupContext.pathName - ? fallbackTitle - : draftGroupContext.pathName + const publishPathName = draftGroupContext.pathName + ? draftGroupContext.pathName + : getDefaultShortname(docTitle, publishedId) if (publishPathName) { await grpcClient.groups.updateGroup({ id: draftGroupContext.groupId, @@ -299,12 +307,24 @@ export function usePublishDraft( [publishPathName]: `${publishedId}?v=${pub.version}`, }, }) + return { + pub, + pubContext: { + key: 'group', + groupId: draftGroupContext.groupId, + pathName: publishPathName, + }, + } } } - return pub + return {pub, pubContext: draftPubContext} }, - onSuccess: (pub: Publication, variables, context) => { - const documentId = pub.document?.id + onSuccess: ( + result: {pub: Publication; pubContext: PublicationRouteContext}, + variables, + context, + ) => { + const documentId = result.pub.document?.id client.setQueryData([queryKeys.EDITOR_DRAFT, documentId], () => null) invalidate([queryKeys.GET_PUBLICATION_LIST]) invalidate([queryKeys.PUBLICATION_CITATIONS]) @@ -316,11 +336,12 @@ export function usePublishDraft( invalidate([queryKeys.GET_SITE_PUBLICATIONS]) if (draftGroupContext) { invalidate([queryKeys.GET_GROUP_CONTENT, draftGroupContext.groupId]) + invalidate([queryKeys.GET_GROUPS_FOR_DOCUMENT, documentId]) } - opts?.onSuccess?.(pub, variables, context) + opts?.onSuccess?.(result, variables, context) setTimeout(() => { - client.removeQueries([queryKeys.EDITOR_DRAFT, pub.document?.id]) + client.removeQueries([queryKeys.EDITOR_DRAFT, result.pub.document?.id]) // otherwise it will re-query for a draft that no longer exists and an error happens }, 250) }, From f1175d8cbbac1b2fbd611d8ff3f24e9a1f3d623b Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Wed, 13 Sep 2023 15:02:04 -0700 Subject: [PATCH 4/4] Improved URL +appRoute handling --- frontend/apps/site/publication-page.tsx | 3 +- .../app/src/components/quick-switcher.tsx | 21 ++++--- .../packages/app/src/editor/embed-block.tsx | 9 +-- frontend/packages/app/src/open-url.ts | 55 +++++++++---------- .../shared/src/utils/entity-id-url.ts | 34 +++++------- 5 files changed, 62 insertions(+), 60 deletions(-) diff --git a/frontend/apps/site/publication-page.tsx b/frontend/apps/site/publication-page.tsx index f8a0e2b6eb..22ad3e9bb4 100644 --- a/frontend/apps/site/publication-page.tsx +++ b/frontend/apps/site/publication-page.tsx @@ -2,7 +2,6 @@ import { Account, EmbedBlock, getCIDFromIPFSUrl, - extractEntityId, HeadingBlock, ImageBlock, InlineContent, @@ -168,7 +167,7 @@ function PublicationContextSidebar({ }) { const groupContent = trpc.group.listContent.useQuery( { - groupEid: extractEntityId(group?.id || '')?.[1] || '', + groupId: group?.id || '', }, {enabled: !!group?.id}, ) diff --git a/frontend/packages/app/src/components/quick-switcher.tsx b/frontend/packages/app/src/components/quick-switcher.tsx index f22cfffbbb..ecc018ab79 100644 --- a/frontend/packages/app/src/components/quick-switcher.tsx +++ b/frontend/packages/app/src/components/quick-switcher.tsx @@ -4,7 +4,11 @@ import { } from '@mintter/app/src/models/documents' import {fetchWebLink} from '@mintter/app/src/models/web-links' import {useNavigate} from '@mintter/app/src/utils/navigation' -import {isHypermediaScheme} from '@mintter/shared' +import { + HYPERMEDIA_SCHEME, + isHypermediaScheme, + unpackHmId, +} from '@mintter/shared' import {Spinner, YStack} from '@mintter/ui' import {useListen} from '@mintter/app/src/app-context' import {Command} from 'cmdk' @@ -12,7 +16,7 @@ import {useState} from 'react' import {toast} from 'react-hot-toast' import './quick-switcher.css' import {useAppContext} from '@mintter/app/src/app-context' -import {hmIdToAppRoute} from '../open-url' +import {unpackHmIdWithAppRoute} from '../open-url' export default function QuickSwitcher() { const [open, setOpen] = useState(false) @@ -61,12 +65,15 @@ export default function QuickSwitcher() { key="mtt-link" value={search} onSelect={() => { - if (isHypermediaScheme(search)) { - const navRoute = hmIdToAppRoute(search) - - if (navRoute) { + const searched = unpackHmIdWithAppRoute(search) + console.log('== ~ QuickSwitcher ~ searched', searched) + if ( + searched?.scheme === HYPERMEDIA_SCHEME || + searched?.hostname === 'hyper.media' + ) { + if (searched?.navRoute) { setOpen(false) - navigate(navRoute) + navigate(searched?.navRoute) } else { console.log('== ~ QuickSwitcher ~ Querying Web URL', search) setActionPromise( diff --git a/frontend/packages/app/src/editor/embed-block.tsx b/frontend/packages/app/src/editor/embed-block.tsx index 5c5c4bec5b..7f67550280 100644 --- a/frontend/packages/app/src/editor/embed-block.tsx +++ b/frontend/packages/app/src/editor/embed-block.tsx @@ -21,6 +21,7 @@ import { isHypermediaScheme, serverBlockToEditorInline, unpackDocId, + unpackHmId, } from '@mintter/shared' import {SizableText, Spinner, Text, XStack, YStack} from '@mintter/ui' import {AlertCircle} from '@tamagui/lucide-icons' @@ -31,7 +32,7 @@ import {createReactBlockSpec} from '../blocknote-react' import {HMBlockSchema, hmBlockSchema} from '../client/schema' import {BACKEND_FILE_URL} from '../constants' import {usePublication} from '../models/documents' -import {hmIdToAppRoute, useOpenUrl} from '../open-url' +import {unpackHmIdWithAppRoute, useOpenUrl} from '../open-url' function InlineContentView({inline}: {inline: InlineContent[]}) { const openUrl = useOpenUrl() @@ -174,9 +175,9 @@ function EmbedPresentation({ if (editor?.isEditable) { return } - const route = hmIdToAppRoute(block.props.ref) - if (route) { - spawn(route) + const unpacked = unpackHmIdWithAppRoute(block.props.ref) + if (unpacked?.navRoute && unpacked?.scheme === 'hm') { + spawn(unpacked?.navRoute) } }} > diff --git a/frontend/packages/app/src/open-url.ts b/frontend/packages/app/src/open-url.ts index 70d6d5d5df..133884baf2 100644 --- a/frontend/packages/app/src/open-url.ts +++ b/frontend/packages/app/src/open-url.ts @@ -1,33 +1,33 @@ import {useAppContext} from '@mintter/app/src/app-context' import {NavRoute, useNavigate} from '@mintter/app/src/utils/navigation' -import {createHmId, isHypermediaScheme, unpackHmId} from '@mintter/shared' +import {UnpackedHypermediaId, createHmId, unpackHmId} from '@mintter/shared' import {useMemo} from 'react' -export function hmIdToAppRoute(hmId: string): NavRoute | undefined { +export function unpackHmIdWithAppRoute( + hmId: string, +): (UnpackedHypermediaId & {navRoute?: NavRoute}) | null { const hmIds = unpackHmId(hmId) - - let pubRoute: NavRoute | undefined = undefined - if (hmIds?.scheme === 'hm') { - if (hmIds?.type === 'd') { - pubRoute = { - key: 'publication', - documentId: createHmId('d', hmIds.eid), - versionId: hmIds.version, - blockId: hmIds.blockRef, - } - } else if (hmIds?.type === 'g') { - pubRoute = { - key: 'group', - groupId: createHmId('g', hmIds.eid), - } - } else if (hmIds?.type === 'a') { - pubRoute = { - key: 'account', - accountId: hmIds.eid, - } + if (!hmIds) return null + let navRoute: NavRoute | undefined = undefined + if (hmIds?.type === 'd') { + navRoute = { + key: 'publication', + documentId: createHmId('d', hmIds.eid), + versionId: hmIds.version, + blockId: hmIds.blockRef, + } + } else if (hmIds?.type === 'g') { + navRoute = { + key: 'group', + groupId: createHmId('g', hmIds.eid), + } + } else if (hmIds?.type === 'a') { + navRoute = { + key: 'account', + accountId: hmIds.eid, } } - return pubRoute + return {...hmIds, navRoute} } export function useOpenUrl() { @@ -37,13 +37,12 @@ export function useOpenUrl() { return useMemo(() => { return (url?: string, newWindow?: boolean) => { if (!url) return - - const pubRoute = hmIdToAppRoute(url) - if (pubRoute) { + const dest = unpackHmIdWithAppRoute(url) + if (dest?.navRoute) { if (newWindow) { - spawn(pubRoute) + spawn(dest?.navRoute) } else { - push(pubRoute) + push(dest?.navRoute) } return } else { diff --git a/frontend/packages/shared/src/utils/entity-id-url.ts b/frontend/packages/shared/src/utils/entity-id-url.ts index bf09c89ad9..e797d4edf6 100644 --- a/frontend/packages/shared/src/utils/entity-id-url.ts +++ b/frontend/packages/shared/src/utils/entity-id-url.ts @@ -13,22 +13,6 @@ export function getPublicDocUrl(docId: string, version?: string | undefined) { return webUrl } -export function extractEntityId(id: string): [string, string] | null { - // input is like hm://x/abcd. output is ['x', 'abcd'] - const m = id.match(/^hm:\/\/([^/]+)\/(.+)$/) - if (!m) return null - const entityType = m[1] - const entityEId = m[2] - return [entityType, entityEId] -} - -export function isValidSiteEntity(entityType: string) { - if (entityType === 'a') return true - if (entityType === 'd') return true - if (entityType === 'g') return true - return false -} - export function createPublicWebHmUrl( type: keyof typeof HYPERMEDIA_ENTITY_TYPES, eid: string, @@ -102,12 +86,22 @@ function inKeys( return null } -export function unpackHmId(hypermediaId: string) { +export type UnpackedHypermediaId = { + type: keyof typeof HYPERMEDIA_ENTITY_TYPES + eid: string + version?: string + blockRef?: string + hostname?: string + scheme?: string +} + +export function unpackHmId(hypermediaId: string): UnpackedHypermediaId | null { const parsed = parseCustomURL(hypermediaId) if (parsed?.scheme === HYPERMEDIA_SCHEME) { const type = inKeys(parsed?.path[0], HYPERMEDIA_ENTITY_TYPES) const eid = parsed?.path[1] const version = parsed?.query.v + if (!type) return null return { type, eid, @@ -122,6 +116,7 @@ export function unpackHmId(hypermediaId: string) { const eid = parsed?.path[2] const version = parsed?.query.v let hostname = parsed?.path[0] + if (!type) return null return { type, eid, @@ -134,7 +129,9 @@ export function unpackHmId(hypermediaId: string) { return null } -export function unpackDocId(inputUrl: string) { +export function unpackDocId( + inputUrl: string, +): (UnpackedHypermediaId & {docId: string}) | null { const unpackedHm = unpackHmId(inputUrl) if (!unpackedHm?.eid) return null if (unpackedHm.type !== 'd') { @@ -157,7 +154,6 @@ export function isHypermediaScheme(url?: string) { export function isPublicGatewayLink(text: string) { const matchesGateway = text.indexOf(HYPERMEDIA_PUBLIC_WEB_GATEWAY) === 0 - console.log('PATH', text.split(HYPERMEDIA_PUBLIC_WEB_GATEWAY)[1]) return !!matchesGateway }