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

Delete Content #1692

Merged
merged 27 commits into from
Apr 23, 2024
Merged
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e4ac2f4
wip(daemon): delete unreferenced entities
juligasa Apr 8, 2024
deca369
wip(daemon): prevent syncing back deleted documents
juligasa Apr 8, 2024
dd1f1cb
wip(daemon): list deleted documents
juligasa Apr 8, 2024
96007b6
wip(daemon): new delete entity
juligasa Apr 9, 2024
9664bfd
wip(daemos): tests
juligasa Apr 9, 2024
287a482
wip(daemon): tests
juligasa Apr 10, 2024
cc2d285
wip(daemon): proper testing
juligasa Apr 10, 2024
9115789
wiip(daemon): fix tests
juligasa Apr 11, 2024
c41c5a6
wip(daemon): update protos
juligasa Apr 11, 2024
50a64d8
wip(daemon): restore deleted entities
juligasa Apr 12, 2024
425e80c
wip(daemon): remove old deletePublication
juligasa Apr 15, 2024
345cbb7
wip(daemon): deletion with links tests
juligasa Apr 17, 2024
1ba3d47
wip(daemon): include comment tests
juligasa Apr 17, 2024
25628a6
fix(daemon): getting back comments
juligasa Apr 17, 2024
b18c286
Delete Dialog for all Entities
ericvicenti Apr 18, 2024
cf03427
fix(daemon): remove comments first
juligasa Apr 19, 2024
9cf6162
wip(daemon): list deleted entities test
juligasa Apr 22, 2024
6578f07
Improve delete workflow
ericvicenti Apr 22, 2024
fa3d17a
fix(deamon): lint
juligasa Apr 23, 2024
4786e0c
fix(deamon): remove unused params
juligasa Apr 23, 2024
9d0b0d4
fix(daemon): linting
juligasa Apr 23, 2024
bdb48b3
fix(daemon): rename + tests
juligasa Apr 23, 2024
e85f136
fix(daemon): not indexing deleted accounts
juligasa Apr 23, 2024
a77370f
wip(daemon): share connection
juligasa Apr 23, 2024
a423fa7
fix(daemon): remove nested connections
juligasa Apr 23, 2024
76fedc7
fix(daemon):lint
juligasa Apr 23, 2024
f852795
fix(daemon): remove non-unitary tests
juligasa Apr 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
wip(daemon): new delete entity
juligasa committed Apr 22, 2024
commit 96007b60470a84445aff54f895a2312c8b528875
65 changes: 0 additions & 65 deletions backend/daemon/api/documents/v1alpha/documents.go
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"math"
"mintter/backend/core"
@@ -631,45 +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) {
var meta string
var qGetResourceMetadata = dqb.Str(`
SELECT meta from meta_view
WHERE iri = :eid
`)

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

eid := hyper.EntityID(in.DocumentId)
conn, cancel, err := api.db.Conn(ctx)
if err != nil {
return nil, err
}
defer cancel()

err = sqlitex.Exec(conn, qGetResourceMetadata(), func(stmt *sqlite.Stmt) error {
meta = stmt.ColumnText(0)
return nil
}, in.DocumentId)
if err != nil {
return nil, err
}

err = api.blobs.DeleteEntity(ctx, eid, in.Reason, meta)
if err != nil {
if errors.Is(err, hyper.ErrEntityNotFound) {
return nil, err
}
return nil, status.Errorf(codes.Unimplemented, "Resource can't be deleted because it's referenced somewhere else")
// TODO(juligasa): Empty the data field, size -1 and manually remove links
}

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 == "" {
@@ -934,31 +894,6 @@ func (api *Server) ListPublications(ctx context.Context, in *documents.ListPubli
return resp, nil
}

// ListPublications implements the corresponding gRPC method.
func (api *Server) ListDeletedPublications(ctx context.Context, in *documents.ListDeletedPublicationsRequest) (*documents.ListDeletedPublicationsResponse, error) {
conn, cancel, err := api.db.Conn(ctx)
if err != nil {
return nil, fmt.Errorf("Can't get a connection from the db: %w", err)
}
defer cancel()
resp := &documents.ListDeletedPublicationsResponse{
DeletedPublications: make([]*documents.DeletedPublication, 0),
}
list, err := hypersql.EntitiesListRemovedRecords(conn)
if err != nil {
return nil, err
}
for _, entity := range list {
resp.DeletedPublications = append(resp.DeletedPublications, &documents.DeletedPublication{
Eid: entity.DeletedResourcesIRI,
DeletedTime: &timestamppb.Timestamp{Seconds: entity.DeletedResourcesDeleteTime},
DeletedReason: entity.DeletedResourcesReason,
Metadata: entity.DeletedResourcesMeta,
})
}
return resp, nil
}

// ListAccountPublications implements the corresponding gRPC method.
func (api *Server) ListAccountPublications(ctx context.Context, in *documents.ListAccountPublicationsRequest) (*documents.ListPublicationsResponse, error) {
if in.AccountId == "" {
36 changes: 0 additions & 36 deletions backend/daemon/api/documents/v1alpha/documents_test.go
Original file line number Diff line number Diff line change
@@ -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()

69 changes: 64 additions & 5 deletions backend/daemon/api/entities/v1alpha/entities.go
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"math"
"mintter/backend/core"
@@ -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"
)

@@ -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
}
@@ -400,6 +398,67 @@ 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.DeleteEntity(ctx, eid, in.Reason, meta)
if err != nil {
if errors.Is(err, hyper.ErrEntityNotFound) {
return nil, err
}
return nil, status.Errorf(codes.Unimplemented, "Entity can't be deleted because it's referenced somewhere else")
// TODO(juligasa): Empty the data field, size -1 and manually remove links
}

return &emptypb.Empty{}, nil
}

// ListDeletedEntities implements the corresponding gRPC method.
func (api *Server) ListDeletedEntities(ctx context.Context, in *entities.ListDeletedEntitiesRequest) (*entities.ListDeletedEntitiesResponse, error) {
juligasa marked this conversation as resolved.
Show resolved Hide resolved

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,
DeletedTime: &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;`)
77 changes: 77 additions & 0 deletions backend/daemon/api/entities/v1alpha/entities_test.go
Original file line number Diff line number Diff line change
@@ -3,18 +3,24 @@ package entities
import (
"context"
"fmt"
"mintter/backend/core"
"mintter/backend/core/coretest"
daemon "mintter/backend/daemon/api/daemon/v1alpha"
documents "mintter/backend/daemon/api/documents/v1alpha"
"mintter/backend/daemon/storage"
documentsproto "mintter/backend/genproto/documents/v1alpha"
entities "mintter/backend/genproto/entities/v1alpha"
"mintter/backend/hyper"
"mintter/backend/logging"
"mintter/backend/pkg/future"
"mintter/backend/pkg/must"
"mintter/backend/testutil"
"strings"
"sync"
"testing"
"time"

"crawshaw.io/sqlite/sqlitex"
"github.com/ipfs/go-cid"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
@@ -170,3 +176,74 @@ func TestEntityTimeline(t *testing.T) {

testutil.ProtoEqual(t, want, timeline, "timeline must match")
}

func TestDeleteEntity(t *testing.T) {
db := storage.MakeTestDB(t)

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

blobs := hyper.NewStorage(db, zap.NewNop())
juligasa marked this conversation as resolved.
Show resolved Hide resolved
api := NewServer(blobs, nil)

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

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

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

deleted, err := api.DeleteEntity(ctx, &entities.DeleteEntityRequest{Id: doc.Id})
require.NoError(t, err)
require.NotNil(t, deleted)

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

pub, err := docsapi.GetPublication(ctx, &documentsproto.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 newTestDocsAPI(t *testing.T, db *sqlitex.Pool, name string) *documents.Server {
u := coretest.NewTester("alice")
juligasa marked this conversation as resolved.
Show resolved Hide resolved

fut := future.New[core.Identity]()
require.NoError(t, fut.Resolve(u.Identity))

srv := documents.NewServer(fut.ReadOnly, db, nil, nil, "debug")
bs := hyper.NewStorage(db, logging.New("mintter/hyper", "debug"))
_, err := daemon.Register(context.Background(), bs, u.Account, u.Device.PublicKey, time.Now())
require.NoError(t, err)

// since we cannot do _, err = srv.me.Await(context.Background())
time.Sleep(10 * time.Millisecond)

return srv
}

func updateDraft(ctx context.Context, t *testing.T, docapi *documents.Server, id string, updates []*documentsproto.DocumentChange) *documentsproto.Document {
_, err := docapi.UpdateDraft(ctx, &documentsproto.UpdateDraftRequest{
DocumentId: id,
Changes: updates,
})
require.NoError(t, err, "failed to update draft")

draft, err := docapi.GetDraft(ctx, &documentsproto.GetDraftRequest{DocumentId: id})
require.NoError(t, err, "failed to get draft after update")

return draft
}
Loading