Skip to content
This repository has been archived by the owner on Jan 2, 2025. It is now read-only.

Commit

Permalink
Delete Entities
Browse files Browse the repository at this point in the history
Delete entities. List deleted and undo deletion
  • Loading branch information
juligasa authored Apr 23, 2024
1 parent f0f80e5 commit c4e4618
Show file tree
Hide file tree
Showing 42 changed files with 2,450 additions and 828 deletions.
3 changes: 2 additions & 1 deletion backend/daemon/api/documents/v1alpha/comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/url"
"strings"

"crawshaw.io/sqlite"
"github.com/ipfs/go-cid"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand Down Expand Up @@ -133,7 +134,7 @@ func (srv *Server) ListComments(ctx context.Context, in *documents.ListCommentsR
}

resp := &documents.ListCommentsResponse{}
if err := srv.blobs.ForEachComment(ctx, in.Target, func(c cid.Cid, cmt hyper.Comment) error {
if err := srv.blobs.ForEachComment(ctx, in.Target, func(c cid.Cid, cmt hyper.Comment, _ *sqlite.Conn) error {
pb, err := commentToProto(ctx, srv.blobs, c, cmt)
if err != nil {
return fmt.Errorf("failed to convert comment %s to proto", c.String())
Expand Down
15 changes: 0 additions & 15 deletions backend/daemon/api/documents/v1alpha/documents.go
Original file line number Diff line number Diff line change
Expand Up @@ -630,21 +630,6 @@ func (api *Server) loadPublication(ctx context.Context, docid hyper.EntityID, ve
}, nil
}

// DeletePublication implements the corresponding gRPC method.
func (api *Server) DeletePublication(ctx context.Context, in *documents.DeletePublicationRequest) (*emptypb.Empty, error) {
if in.DocumentId == "" {
return nil, status.Errorf(codes.InvalidArgument, "must specify publication ID to delete")
}

eid := hyper.EntityID(in.DocumentId)

if err := api.blobs.DeleteEntity(ctx, eid); err != nil {
return nil, err
}

return &emptypb.Empty{}, nil
}

// PushPublication implements the corresponding gRPC method.
func (api *Server) PushPublication(ctx context.Context, in *documents.PushPublicationRequest) (*emptypb.Empty, error) {
if in.DocumentId == "" {
Expand Down
38 changes: 0 additions & 38 deletions backend/daemon/api/documents/v1alpha/documents_bugs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,44 +254,6 @@ func TestBug_MoveBockWithoutReplacement(t *testing.T) {
require.Len(t, dlist.Documents, 1)
}

func TestBug_MissingLinkTarget(t *testing.T) {
t.Parallel()

api := newTestDocsAPI(t, "alice")
ctx := context.Background()

draft, err := api.CreateDraft(ctx, &CreateDraftRequest{})
require.NoError(t, err)
updated := updateDraft(ctx, t, api, draft.Id, []*DocumentChange{
{Op: &DocumentChange_SetTitle{SetTitle: "My new document title"}},
{Op: &DocumentChange_MoveBlock_{MoveBlock: &DocumentChange_MoveBlock{BlockId: "b1"}}},
{Op: &DocumentChange_ReplaceBlock{ReplaceBlock: &Block{
Id: "b1",
Type: "statement",
Text: "Hello world!",
Annotations: []*Annotation{
{
Type: "link",
Attributes: map[string]string{
"url": "mtt://bafy2bzaceaemtzyq7gj6fa5jn4xhfq6yp657j5dpoqvh6bio4kk4bi2wmoroy/baeaxdiheaiqfsiervpfvbohhvjgnkcto3f5p4alwe4k46fr334vlw4n5jaknnqa/MIWneLC1",
},
Starts: []int32{0},
Ends: []int32{5},
},
},
}}},
})
require.NoError(t, err)
require.NotNil(t, updated)
published, err := api.PublishDraft(ctx, &PublishDraftRequest{DocumentId: draft.Id})
require.NoError(t, err)
require.NotNil(t, published)

linked, err := api.GetPublication(ctx, &GetPublicationRequest{DocumentId: "bafy2bzaceaemtzyq7gj6fa5jn4xhfq6yp657j5dpoqvh6bio4kk4bi2wmoroy"})
require.Error(t, err)
require.Nil(t, linked)
}

func TestBug_BlockRevisionMustUpdate(t *testing.T) {
t.Parallel()

Expand Down
36 changes: 0 additions & 36 deletions backend/daemon/api/documents/v1alpha/documents_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -742,42 +742,6 @@ func TestGetPublicationWithDraftID(t *testing.T) {
require.Nil(t, published, "draft is not a publication")
}

func TestAPIDeletePublication(t *testing.T) {
api := newTestDocsAPI(t, "alice")
ctx := context.Background()

doc, err := api.CreateDraft(ctx, &documents.CreateDraftRequest{})
require.NoError(t, err)
doc = updateDraft(ctx, t, api, doc.Id, []*documents.DocumentChange{
{Op: &documents.DocumentChange_SetTitle{SetTitle: "My new document title"}}},
)

_, err = api.PublishDraft(ctx, &documents.PublishDraftRequest{DocumentId: doc.Id})
require.NoError(t, err)

list, err := api.ListPublications(ctx, &documents.ListPublicationsRequest{})
require.NoError(t, err)
require.Len(t, list.Publications, 1)

deleted, err := api.DeletePublication(ctx, &documents.DeletePublicationRequest{DocumentId: doc.Id})
require.NoError(t, err)
require.NotNil(t, deleted)

list, err = api.ListPublications(ctx, &documents.ListPublicationsRequest{})
require.NoError(t, err)
require.Len(t, list.Publications, 0)

pub, err := api.GetPublication(ctx, &documents.GetPublicationRequest{DocumentId: doc.Id})
require.Error(t, err, "must fail to get deleted publication")
_ = pub

// TODO: fix status codes.
// s, ok := status.FromError(err)
// require.True(t, ok)
// require.Nil(t, pub)
// require.Equal(t, codes.NotFound, s.Code())
}

func TestPublisherAndEditors(t *testing.T) {
t.Parallel()

Expand Down
128 changes: 123 additions & 5 deletions backend/daemon/api/entities/v1alpha/entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"math"
"mintter/backend/core"
Expand All @@ -27,6 +28,7 @@ import (
"golang.org/x/exp/slices"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
)

Expand Down Expand Up @@ -369,17 +371,13 @@ func (api *Server) SearchEntities(ctx context.Context, in *entities.SearchEntiti
var owners []string
const limit = 30
if err := api.blobs.Query(ctx, func(conn *sqlite.Conn) error {
err := sqlitex.Exec(conn, qGetEntityTitles(), func(stmt *sqlite.Stmt) error {
return sqlitex.Exec(conn, qGetEntityTitles(), func(stmt *sqlite.Stmt) error {
titles = append(titles, stmt.ColumnText(0))
iris = append(iris, stmt.ColumnText(1))
ownerID := core.Principal(stmt.ColumnBytes(2)).String()
owners = append(owners, ownerID)
return nil
})
if err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
Expand All @@ -400,6 +398,126 @@ func (api *Server) SearchEntities(ctx context.Context, in *entities.SearchEntiti
return &entities.SearchEntitiesResponse{Entities: matchingEntities}, nil
}

// DeleteEntity implements the corresponding gRPC method.
func (api *Server) DeleteEntity(ctx context.Context, in *entities.DeleteEntityRequest) (*emptypb.Empty, error) {
var meta string
var qGetResourceMetadata = dqb.Str(`
SELECT meta from meta_view
WHERE iri = :eid
`)

if in.Id == "" {
return nil, status.Errorf(codes.InvalidArgument, "must specify entity ID to delete")
}

eid := hyper.EntityID(in.Id)

err := api.blobs.Query(ctx, func(conn *sqlite.Conn) error {
return sqlitex.Exec(conn, qGetResourceMetadata(), func(stmt *sqlite.Stmt) error {
meta = stmt.ColumnText(0)
return nil
}, in.Id)
})
if err != nil {
return nil, err
}
err = api.blobs.ForEachComment(ctx, eid.String(), func(c cid.Cid, cmt hyper.Comment, conn *sqlite.Conn) error {
referencedDocument := strings.Split(cmt.Target, "?v=")[0]
if referencedDocument == eid.String() {
_, err = hypersql.BlobsDelete(conn, c.Hash())
if err != nil {
if err = hypersql.BlobsEmptyByHash(conn, c.Hash()); err != nil {
return err
}
}
if cmt.RepliedComment.String() != "" {
_, err = hypersql.BlobsDelete(conn, cmt.RepliedComment.Hash())
if err != nil {
if err = hypersql.BlobsEmptyByHash(conn, cmt.RepliedComment.Hash()); err != nil {
return err
}
}
}

return nil
}
return nil
})

err = api.blobs.DeleteEntity(ctx, eid)
if err != nil {
if errors.Is(err, hyper.ErrEntityNotFound) {
return nil, err
}

_, err = &emptypb.Empty{}, api.blobs.Query(ctx, func(conn *sqlite.Conn) error {
return hypersql.BlobsEmptyByEID(conn, in.Id)
})
if err != nil {
return &emptypb.Empty{}, err
}

_, err = &emptypb.Empty{}, api.blobs.Query(ctx, func(conn *sqlite.Conn) error {
return hypersql.BlobsStructuralDelete(conn, in.Id)
})
if err != nil {
return &emptypb.Empty{}, err
}
}
_, err = &emptypb.Empty{}, api.blobs.Query(ctx, func(conn *sqlite.Conn) error {
return sqlitex.WithTx(conn, func() error {
res, err := hypersql.EntitiesInsertRemovedRecord(conn, eid.String(), in.Reason, meta)
if err != nil {
return err
}
if res.ResourceEID != eid.String() {
return fmt.Errorf("%w: %s", hyper.ErrEntityNotFound, eid)
}

return nil
})
})
return &emptypb.Empty{}, err
}

// UndeleteEntity implements the corresponding gRPC method.
func (api *Server) UndeleteEntity(ctx context.Context, in *entities.UndeleteEntityRequest) (*emptypb.Empty, error) {
if in.Id == "" {
return nil, status.Errorf(codes.InvalidArgument, "must specify entity ID to restore")
}

eid := hyper.EntityID(in.Id)

return &emptypb.Empty{}, api.blobs.Query(ctx, func(conn *sqlite.Conn) error {
return hypersql.EntitiesDeleteRemovedRecord(conn, eid.String())
})
}

// ListDeletedEntities implements the corresponding gRPC method.
func (api *Server) ListDeletedEntities(ctx context.Context, _ *entities.ListDeletedEntitiesRequest) (*entities.ListDeletedEntitiesResponse, error) {
resp := &entities.ListDeletedEntitiesResponse{
DeletedEntities: make([]*entities.DeletedEntity, 0),
}

err := api.blobs.Query(ctx, func(conn *sqlite.Conn) error {
list, err := hypersql.EntitiesListRemovedRecords(conn)
if err != nil {
return err
}
for _, entity := range list {
resp.DeletedEntities = append(resp.DeletedEntities, &entities.DeletedEntity{
Id: entity.DeletedResourcesIRI,
DeleteTime: &timestamppb.Timestamp{Seconds: entity.DeletedResourcesDeleteTime},
DeletedReason: entity.DeletedResourcesReason,
Metadata: entity.DeletedResourcesMeta,
})
}
return nil
})

return resp, err
}

var qGetEntityTitles = dqb.Str(`
SELECT meta, iri, principal
FROM meta_view;`)
Expand Down
Loading

0 comments on commit c4e4618

Please sign in to comment.