From db00e0dbf9fc4b3b99b5193c62929b66ef7bc5c8 Mon Sep 17 00:00:00 2001 From: Lewis Date: Tue, 14 Apr 2026 09:06:18 +0000 Subject: [PATCH] appview: ingester and indexers use repoDID Lewis: May this revision serve well! --- appview/ingester.go | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------- appview/ingester_repo.go | 332 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ appview/ingester_repo_test.go | 457 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ appview/indexer/notifier.go | 30 +++++++++++++++++++----------- appview/indexer/issues/indexer.go | 10 +++++----- appview/indexer/issues/indexer_test.go | 60 ++++++++++++++++++++++++++++++------------------------------ appview/indexer/pulls/indexer.go | 10 +++++----- appview/indexer/repos/indexer.go | 8 ++++---- 8 file(s) changed, 941 insertion(s)(+), 82 deletion(s)(-) diff --git a/appview/ingester.go b/appview/ingester.go --- a/appview/ingester.go +++ b/appview/ingester.go @@ -12,6 +12,7 @@ "net/http" "net/url" "slices" + "strings" "sync" "time" @@ -27,6 +28,7 @@ "tangled.org/core/appview/config" "tangled.org/core/appview/db" "tangled.org/core/appview/models" + "tangled.org/core/appview/notify" "tangled.org/core/appview/serververify" "tangled.org/core/appview/validator" "tangled.org/core/idresolver" @@ -42,6 +44,7 @@ Config *config.Config Logger *slog.Logger Validator *validator.Validator + Notifier notify.Notifier } type processFunc func(ctx context.Context, e *jmodels.Event) error @@ -97,6 +100,8 @@ err = i.ingestLabelDefinition(e) case tangled.LabelOpNSID: err = i.ingestLabelOp(e) + case tangled.RepoNSID: + err = i.ingestRepo(ctx, e) } l = i.Logger.With("nsid", e.Commit.Collection) } @@ -114,6 +119,59 @@ } } +func (i *Ingester) resolveRepoRef(ref string) (*models.Repo, error) { + if strings.HasPrefix(ref, "did:") { + return db.GetRepoByDid(i.Db, ref) + } + return db.GetRepoByAtUri(i.Db, ref) +} + +func (i *Ingester) resolveOldFormatStar(raw json.RawMessage, star *models.Star, l *slog.Logger) (bool, error) { + var legacy struct { + Subject *string `json:"subject"` + SubjectDid *string `json:"subjectDid"` + } + if err := json.Unmarshal(raw, &legacy); err != nil { + return false, err + } + + switch { + case legacy.SubjectDid != nil: + repo, err := i.resolveRepoRef(*legacy.SubjectDid) + if err != nil { + l.Warn("skipping old-format star for unknown repo", "subjectDid", *legacy.SubjectDid) + return false, nil + } + star.SubjectType = models.StarSubjectRepo + star.Subject = repo.RepoDid + return true, nil + + case legacy.Subject != nil: + uri, err := syntax.ParseATURI(*legacy.Subject) + if err != nil { + return false, fmt.Errorf("invalid old-format star subject: %w", err) + } + switch uri.Collection().String() { + case tangled.RepoNSID: + repo, err := db.GetRepoByAtUri(i.Db, uri.String()) + if err != nil { + l.Warn("skipping old-format star for unknown repo", "subject", *legacy.Subject) + return false, nil + } + star.SubjectType = models.StarSubjectRepo + star.Subject = repo.RepoDid + return true, nil + default: + star.SubjectType = models.StarSubjectString + star.Subject = *legacy.Subject + return true, nil + } + + default: + return false, fmt.Errorf("old-format star has neither subject nor subjectDid") + } +} + func (i *Ingester) ingestStar(ctx context.Context, e *jmodels.Event) error { var err error did := e.Did @@ -123,15 +181,9 @@ switch e.Commit.Operation { case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate: - var subjectUri syntax.ATURI - raw := json.RawMessage(e.Commit.Record) record := tangled.FeedStar{} - err := json.Unmarshal(raw, &record) - if err != nil { - l.Error("invalid record", "err", err) - return err - } + unmarshalErr := json.Unmarshal(raw, &record) star := &models.Star{ Did: did, @@ -139,29 +191,36 @@ } switch { - case record.SubjectDid != nil: - repo, repoErr := db.GetRepo(i.Db, orm.FilterEq("repo_did", *record.SubjectDid)) - if repoErr == nil { - subjectUri = repo.RepoAt() - star.RepoAt = subjectUri + case unmarshalErr != nil: + resolved, resolveErr := i.resolveOldFormatStar(raw, star, l) + if resolveErr != nil { + l.Error("invalid record", "newFmtErr", unmarshalErr, "oldFmtErr", resolveErr) + return unmarshalErr } - case record.Subject != nil: - subjectUri, err = syntax.ParseATURI(*record.Subject) - if err != nil { - l.Error("invalid record", "err", err) - return err + if !resolved { + return nil } - star.RepoAt = subjectUri - repo, repoErr := db.GetRepoByAtUri(i.Db, subjectUri.String()) - if repoErr == nil && repo.RepoDid != "" { - if enqErr := db.EnqueuePdsRecordMigration(ctx, i.Db, "add-repo-did", syntax.DID(did), syntax.NSID(tangled.FeedStarNSID), syntax.RecordKey(e.Commit.RKey)); enqErr != nil { - l.Warn("failed to enqueue PDS rewrite for star", "err", enqErr, "did", did, "repoDid", repo.RepoDid) - } + + case record.Subject == nil: + return fmt.Errorf("star record has nil subject") + + case record.Subject.FeedStar_Repo != nil: + repo, repoErr := i.resolveRepoRef(record.Subject.FeedStar_Repo.Did) + if repoErr != nil { + l.Warn("skipping star for unknown repo", "did", record.Subject.FeedStar_Repo.Did) + return nil } + star.SubjectType = models.StarSubjectRepo + star.Subject = repo.RepoDid + + case record.Subject.FeedStar_String != nil: + star.SubjectType = models.StarSubjectString + star.Subject = record.Subject.FeedStar_String.Uri + default: - l.Error("star record has neither subject nor subjectDid") - return fmt.Errorf("star record has neither subject nor subjectDid") + return fmt.Errorf("star record has empty subject union") } + err = db.AddStar(i.Db, star) case jmodels.CommitOperationDelete: err = db.DeleteStarByRkey(i.Db, did, e.Commit.RKey) @@ -408,7 +467,7 @@ artifact := models.Artifact{ Did: did, Rkey: e.Commit.RKey, - RepoAt: repo.RepoAt(), + RepoDid: syntax.DID(repo.RepoDid), Tag: plumbing.Hash(record.Tag), CreatedAt: createdAt, BlobCid: cid.Cid(record.Artifact.Ref), @@ -977,8 +1036,11 @@ issue := models.IssueFromRecord(did, rkey, record) - if issue.RepoAt == "" { + if issue.RepoDid == "" { return fmt.Errorf("issue record has no repo field") + } + if _, err := syntax.ParseDID(string(issue.RepoDid)); err != nil { + return fmt.Errorf("issue record repo field is not a valid DID: %w", err) } if err := i.Validator.ValidateIssue(&issue); err != nil { diff --git a/appview/ingester_repo.go b/appview/ingester_repo.go new file mode 100644 --- /dev/null +++ b/appview/ingester_repo.go @@ -0,0 +1,332 @@ +package appview + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "slices" + + "github.com/bluesky-social/indigo/atproto/syntax" + jmodels "github.com/bluesky-social/jetstream/pkg/models" + "tangled.org/core/api/tangled" + "tangled.org/core/appview/db" + "tangled.org/core/appview/models" + "tangled.org/core/orm" +) + +func (i *Ingester) ingestRepo(ctx context.Context, e *jmodels.Event) error { + l := i.Logger.With("handler", "ingestRepo", "did", e.Did, "rkey", e.Commit.RKey) + + switch e.Commit.Operation { + case jmodels.CommitOperationCreate: + return i.ingestRepoCreate(ctx, e) + case jmodels.CommitOperationUpdate: + return i.ingestRepoUpdate(ctx, e) + case jmodels.CommitOperationDelete: + return i.ingestRepoDelete(ctx, e) + default: + l.Info("unknown repo operation", "op", e.Commit.Operation) + return nil + } +} + +func (i *Ingester) ingestRepoCreate(ctx context.Context, e *jmodels.Event) error { + l := i.Logger.With("handler", "ingestRepoCreate", "did", e.Did, "rkey", e.Commit.RKey) + + record := tangled.Repo{} + if err := json.Unmarshal(json.RawMessage(e.Commit.Record), &record); err != nil { + l.Error("invalid record", "err", err) + return err + } + + if record.RepoDid == nil || *record.RepoDid == "" { + l.Info("skipping repo create from non-DID-migrated knot") + return nil + } + repoDid := *record.RepoDid + + _, err := db.GetRepo(i.Db, + orm.FilterEq("did", e.Did), + orm.FilterEq("rkey", e.Commit.RKey), + ) + if err == nil { + l.Info("repo row already exists, skipping create", "did", e.Did, "rkey", e.Commit.RKey) + return nil + } + if !errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("failed to check existing repo: %w", err) + } + + prev, err := db.GetRepoByDid(i.Db, repoDid) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("failed to check existing repoDid: %w", err) + } + + if prev != nil { + l.Info("repoDid exists under different rkey, renaming", + "oldRkey", prev.Rkey, "newRkey", e.Commit.RKey) + + oldRepo := *prev + + tx, txErr := i.Db.Begin() + if txErr != nil { + return fmt.Errorf("failed to begin rename tx: %w", txErr) + } + defer tx.Rollback() + + newName := derefString(record.Name) + if newName == "" { + newName = e.Commit.RKey + } + + if err := db.RenameRepo(tx, e.Did, prev.Rkey, e.Commit.RKey, newName); err != nil { + return fmt.Errorf("failed to rename repo: %w", err) + } + if err := db.RecordRepoRename(tx, e.Did, prev.Rkey, repoDid); err != nil { + return fmt.Errorf("failed to record rename history: %w", err) + } + + renamed := *prev + renamed.Rkey = e.Commit.RKey + renamed.Name = newName + desired := repoFromRecord(&renamed, &record) + if repoMetadataChanged(&renamed, &desired) { + if err := applyRepoMetadata(tx, &renamed, desired); err != nil { + return fmt.Errorf("failed to apply metadata after rename: %w", err) + } + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit rename tx: %w", err) + } + + newRepo, err := db.GetRepo(i.Db, + orm.FilterEq("did", e.Did), + orm.FilterEq("rkey", e.Commit.RKey), + ) + if err != nil { + l.Warn("failed to fetch repo after rename for notification", "err", err) + return nil + } + i.Notifier.RenameRepo(ctx, syntax.DID(e.Did), &oldRepo, newRepo) + return nil + } + + rkey := e.Commit.RKey + name := derefString(record.Name) + if name == "" { + name = rkey + } + + repo := &models.Repo{ + Did: e.Did, + Name: name, + Knot: record.Knot, + Rkey: rkey, + Description: derefString(record.Description), + Website: derefString(record.Website), + Topics: append([]string(nil), record.Topics...), + Source: derefString(record.Source), + Spindle: derefString(record.Spindle), + Labels: append([]string(nil), record.Labels...), + RepoDid: repoDid, + } + + tx, err := i.Db.Begin() + if err != nil { + return fmt.Errorf("failed to begin insert tx: %w", err) + } + defer tx.Rollback() + + if err := db.AddRepo(tx, repo); err != nil { + return fmt.Errorf("failed to insert repo: %w", err) + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit insert tx: %w", err) + } + + i.Notifier.NewRepo(ctx, repo) + return nil +} + +func (i *Ingester) ingestRepoUpdate(ctx context.Context, e *jmodels.Event) error { + l := i.Logger.With("handler", "ingestRepoUpdate", "did", e.Did, "rkey", e.Commit.RKey) + + record := tangled.Repo{} + if err := json.Unmarshal(json.RawMessage(e.Commit.Record), &record); err != nil { + l.Error("invalid record", "err", err) + return err + } + + if record.RepoDid == nil || *record.RepoDid == "" { + l.Info("skipping repo update from non-DID-migrated knot") + return nil + } + + current, err := db.GetRepo(i.Db, + orm.FilterEq("did", e.Did), + orm.FilterEq("rkey", e.Commit.RKey), + ) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + l.Info("skipping repo update for unknown row") + return nil + } + return fmt.Errorf("failed to fetch repo for ingest: %w", err) + } + + desired := repoFromRecord(current, &record) + + if current.Source != desired.Source { + l.Warn("source field changed but mutation is unsupported, ignoring", + "current", current.Source, "desired", desired.Source) + } + + if !repoMetadataChanged(current, &desired) { + return nil + } + + tx, err := i.Db.Begin() + if err != nil { + return fmt.Errorf("failed to begin tx: %w", err) + } + defer tx.Rollback() + + if err := applyRepoMetadata(tx, current, desired); err != nil { + return fmt.Errorf("failed to apply repo metadata: %w", err) + } + return tx.Commit() +} + +func (i *Ingester) ingestRepoDelete(ctx context.Context, e *jmodels.Event) error { + l := i.Logger.With("handler", "ingestRepoDelete", "did", e.Did, "rkey", e.Commit.RKey) + + repo, err := db.GetRepo(i.Db, + orm.FilterEq("did", e.Did), + orm.FilterEq("rkey", e.Commit.RKey), + ) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + l.Info("skipping repo delete for unknown row") + return nil + } + return fmt.Errorf("failed to fetch repo for delete: %w", err) + } + + if err := db.RemoveRepo(i.Db, e.Did, e.Commit.RKey); err != nil { + return fmt.Errorf("failed to delete repo: %w", err) + } + + i.Notifier.DeleteRepo(ctx, repo) + l.Info("deleted repo row") + return nil +} + +func applyRepoMetadata(tx *sql.Tx, current *models.Repo, desired models.Repo) error { + if err := db.PutRepo(tx, desired); err != nil { + return err + } + + if current.Spindle != desired.Spindle { + var spindlePtr *string + if desired.Spindle != "" { + spindlePtr = &desired.Spindle + } + if err := db.UpdateSpindle(tx, desired.RepoDid, spindlePtr); err != nil { + return err + } + } + + if !labelsEqual(current.Labels, desired.Labels) { + if err := reconcileLabels(tx, current, desired); err != nil { + return err + } + } + + return nil +} + +func reconcileLabels(tx *sql.Tx, current *models.Repo, desired models.Repo) error { + added := filterOut(desired.Labels, current.Labels) + removed := filterOut(current.Labels, desired.Labels) + + if err := applyEach(added, func(l string) error { + return db.SubscribeLabel(tx, &models.RepoLabel{ + RepoDid: syntax.DID(desired.RepoDid), + LabelAt: syntax.ATURI(l), + }) + }); err != nil { + return err + } + + return applyEach(removed, func(l string) error { + return db.UnsubscribeLabel(tx, + orm.FilterEq("repo_did", desired.RepoDid), + orm.FilterEq("label_at", l), + ) + }) +} + +func filterOut(items, exclude []string) []string { + return slices.DeleteFunc(slices.Clone(items), func(s string) bool { + return slices.Contains(exclude, s) + }) +} + +func applyEach(items []string, fn func(string) error) error { + for _, item := range items { + if err := fn(item); err != nil { + return err + } + } + return nil +} + +func labelsEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + aSorted := append([]string(nil), a...) + bSorted := append([]string(nil), b...) + slices.Sort(aSorted) + slices.Sort(bSorted) + return slices.Equal(aSorted, bSorted) +} + +func repoFromRecord(current *models.Repo, record *tangled.Repo) models.Repo { + out := *current + out.Name = derefString(record.Name) + if out.Name == "" { + out.Name = current.Rkey + } + out.Knot = record.Knot + out.Description = derefString(record.Description) + out.Website = derefString(record.Website) + out.Topics = append([]string(nil), record.Topics...) + out.Spindle = derefString(record.Spindle) + out.Source = derefString(record.Source) + out.Labels = append([]string(nil), record.Labels...) + if record.RepoDid != nil { + out.RepoDid = *record.RepoDid + } + return out +} + +func repoMetadataChanged(current *models.Repo, desired *models.Repo) bool { + return current.Name != desired.Name || + current.Knot != desired.Knot || + current.Description != desired.Description || + current.Website != desired.Website || + current.TopicStr() != desired.TopicStr() || + current.Spindle != desired.Spindle || + !labelsEqual(current.Labels, desired.Labels) +} + +func derefString(s *string) string { + if s == nil { + return "" + } + return *s +} diff --git a/appview/ingester_repo_test.go b/appview/ingester_repo_test.go new file mode 100644 --- /dev/null +++ b/appview/ingester_repo_test.go @@ -0,0 +1,457 @@ +package appview + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "io" + "log/slog" + "path/filepath" + "testing" + + "github.com/bluesky-social/indigo/atproto/syntax" + jmodels "github.com/bluesky-social/jetstream/pkg/models" + "tangled.org/core/api/tangled" + "tangled.org/core/appview/db" + "tangled.org/core/appview/models" + "tangled.org/core/appview/notify" + "tangled.org/core/orm" +) + +type spyNotifier struct { + notify.BaseNotifier + creates int + deletes int + renames int +} + +func (s *spyNotifier) NewRepo(_ context.Context, _ *models.Repo) { s.creates++ } +func (s *spyNotifier) DeleteRepo(_ context.Context, _ *models.Repo) { s.deletes++ } +func (s *spyNotifier) RenameRepo(_ context.Context, _ syntax.DID, _, _ *models.Repo) { + s.renames++ +} + +func newTestIngester(t *testing.T) (*Ingester, *spyNotifier) { + t.Helper() + path := filepath.Join(t.TempDir(), "test.db") + d, err := db.Make(context.Background(), path) + if err != nil { + t.Fatalf("db.Make: %v", err) + } + t.Cleanup(func() { d.Close() }) + + spy := &spyNotifier{} + ing := &Ingester{ + Db: d, + Logger: slog.New(slog.NewTextHandler(io.Discard, nil)), + Notifier: spy, + } + return ing, spy +} + +func seedRepoRow(t *testing.T, ing *Ingester, did, knot, name, rkey, repoDid string) *models.Repo { + t.Helper() + tx, err := ing.Db.Begin() + if err != nil { + t.Fatalf("Begin: %v", err) + } + repo := &models.Repo{ + Did: did, + Name: name, + Knot: knot, + Rkey: rkey, + RepoDid: repoDid, + } + if err := db.AddRepo(tx, repo); err != nil { + t.Fatalf("AddRepo: %v", err) + } + if err := tx.Commit(); err != nil { + t.Fatalf("Commit: %v", err) + } + return repo +} + +func ptr[T any](v T) *T { return &v } + +func makeEvent(t *testing.T, op string, did, rkey string, record tangled.Repo) *jmodels.Event { + t.Helper() + raw, err := json.Marshal(record) + if err != nil { + t.Fatalf("marshal record: %v", err) + } + return &jmodels.Event{ + Did: did, + Kind: jmodels.EventKindCommit, + Commit: &jmodels.Commit{ + Operation: op, + Collection: tangled.RepoNSID, + RKey: rkey, + Record: raw, + }, + } +} + +func makeDeleteEvent(did, rkey string) *jmodels.Event { + return &jmodels.Event{ + Did: did, + Kind: jmodels.EventKindCommit, + Commit: &jmodels.Commit{ + Operation: jmodels.CommitOperationDelete, + Collection: tangled.RepoNSID, + RKey: rkey, + }, + } +} + +func loadRepo(t *testing.T, ing *Ingester, did, rkey string) *models.Repo { + t.Helper() + r, err := db.GetRepo(ing.Db, + orm.FilterEq("did", did), + orm.FilterEq("rkey", rkey), + ) + if err != nil { + t.Fatalf("GetRepo: %v", err) + } + return r +} + +func TestIngestRepo_CreateInsertsNewRow(t *testing.T) { + ing, spy := newTestIngester(t) + + e := makeEvent(t, jmodels.CommitOperationCreate, "did:plc:akshay", "myrepo", tangled.Repo{ + Knot: "knot.example", + Name: ptr("MyRepo"), + Description: ptr("a test repo"), + RepoDid: ptr("did:plc:repo1"), + }) + + if err := ing.ingestRepo(context.Background(), e); err != nil { + t.Fatalf("ingestRepo: %v", err) + } + + r := loadRepo(t, ing, "did:plc:akshay", "myrepo") + if r.Name != "MyRepo" { + t.Errorf("name = %q, want %q", r.Name, "MyRepo") + } + if r.Description != "a test repo" { + t.Errorf("description = %q", r.Description) + } + if r.RepoDid != "did:plc:repo1" { + t.Errorf("repoDid = %q", r.RepoDid) + } + if spy.creates != 1 { + t.Errorf("NewRepo called %d times, want 1", spy.creates) + } +} + +func TestIngestRepo_CreateSkipsIfRowExists(t *testing.T) { + ing, spy := newTestIngester(t) + seedRepoRow(t, ing, "did:plc:akshay", "knot.example", "myrepo", "myrepo", "did:plc:repo1") + + e := makeEvent(t, jmodels.CommitOperationCreate, "did:plc:akshay", "myrepo", tangled.Repo{ + Knot: "knot.example", + Name: ptr("myrepo"), + RepoDid: ptr("did:plc:repo1"), + }) + + if err := ing.ingestRepo(context.Background(), e); err != nil { + t.Fatalf("ingestRepo: %v", err) + } + if spy.creates != 0 { + t.Errorf("row already exists, NewRepo should not be called but was called %d times", spy.creates) + } +} + +func TestIngestRepo_CreateCascadesRename(t *testing.T) { + ing, spy := newTestIngester(t) + seedRepoRow(t, ing, "did:plc:akshay", "knot.example", "oldname", "oldname", "did:plc:repo1") + + e := makeEvent(t, jmodels.CommitOperationCreate, "did:plc:akshay", "newname", tangled.Repo{ + Knot: "knot.example", + Name: ptr("NewName"), + RepoDid: ptr("did:plc:repo1"), + }) + + if err := ing.ingestRepo(context.Background(), e); err != nil { + t.Fatalf("ingestRepo: %v", err) + } + + _, err := db.GetRepo(ing.Db, + orm.FilterEq("did", "did:plc:akshay"), + orm.FilterEq("rkey", "oldname"), + ) + if !errors.Is(err, sql.ErrNoRows) { + t.Errorf("old rkey row should be gone, got err = %v", err) + } + + r := loadRepo(t, ing, "did:plc:akshay", "newname") + if r.Name != "NewName" { + t.Errorf("name = %q, want %q", r.Name, "NewName") + } + if r.RepoDid != "did:plc:repo1" { + t.Errorf("repoDid = %q", r.RepoDid) + } + + hint, err := db.LookupRepoRename(ing.Db, "did:plc:akshay", "oldname") + if err != nil { + t.Fatalf("LookupRepoRename: %v", err) + } + if hint == nil { + t.Fatal("expected rename history, got nil") + } + + if spy.renames != 1 { + t.Errorf("RenameRepo called %d times, want 1", spy.renames) + } + if spy.creates != 0 { + t.Errorf("rename should not create: NewRepo called %d times, want 0", spy.creates) + } +} + +func TestIngestRepo_CreateNoRepoDidSkipped(t *testing.T) { + ing, spy := newTestIngester(t) + + e := makeEvent(t, jmodels.CommitOperationCreate, "did:plc:akshay", "myrepo", tangled.Repo{ + Knot: "knot.example", + Name: ptr("myrepo"), + }) + + if err := ing.ingestRepo(context.Background(), e); err != nil { + t.Fatalf("ingestRepo: %v", err) + } + if spy.creates != 0 { + t.Errorf("NewRepo called %d times, want 0", spy.creates) + } +} + +func TestIngestRepo_UpdateMetadata(t *testing.T) { + ing, _ := newTestIngester(t) + seedRepoRow(t, ing, "did:plc:akshay", "knot.example", "foo", "foo", "did:plc:repo1") + + e := makeEvent(t, jmodels.CommitOperationUpdate, "did:plc:akshay", "foo", tangled.Repo{ + Knot: "knot.example", + Name: ptr("foo"), + Description: ptr("updated description"), + Website: ptr("https://example.com"), + Topics: []string{"go", "test"}, + RepoDid: ptr("did:plc:repo1"), + }) + + if err := ing.ingestRepo(context.Background(), e); err != nil { + t.Fatalf("ingestRepo: %v", err) + } + + r := loadRepo(t, ing, "did:plc:akshay", "foo") + if r.Description != "updated description" { + t.Errorf("description = %q", r.Description) + } + if r.Website != "https://example.com" { + t.Errorf("website = %q", r.Website) + } + if got := r.TopicStr(); got != "go test" { + t.Errorf("topics = %q", got) + } +} + +func TestIngestRepo_UpdateDisplayName(t *testing.T) { + ing, _ := newTestIngester(t) + seedRepoRow(t, ing, "did:plc:akshay", "knot.example", "foo", "foo", "did:plc:repo1") + + e := makeEvent(t, jmodels.CommitOperationUpdate, "did:plc:akshay", "foo", tangled.Repo{ + Knot: "knot.example", + Name: ptr("Foo"), + RepoDid: ptr("did:plc:repo1"), + }) + + if err := ing.ingestRepo(context.Background(), e); err != nil { + t.Fatalf("ingestRepo: %v", err) + } + + r := loadRepo(t, ing, "did:plc:akshay", "foo") + if r.Name != "Foo" { + t.Errorf("name = %q, want %q", r.Name, "Foo") + } + if r.Rkey != "foo" { + t.Errorf("rkey should be unchanged but got %q, want %q", r.Rkey, "foo") + } +} + +func TestIngestRepo_UpdateNothingChangedNoOp(t *testing.T) { + ing, _ := newTestIngester(t) + seedRepoRow(t, ing, "did:plc:akshay", "knot.example", "foo", "foo", "did:plc:repo1") + + e := makeEvent(t, jmodels.CommitOperationUpdate, "did:plc:akshay", "foo", tangled.Repo{ + Knot: "knot.example", + Name: ptr("foo"), + RepoDid: ptr("did:plc:repo1"), + }) + + if err := ing.ingestRepo(context.Background(), e); err != nil { + t.Fatalf("ingestRepo: %v", err) + } + + r := loadRepo(t, ing, "did:plc:akshay", "foo") + if r.Name != "foo" { + t.Errorf("name = %q, want unchanged %q", r.Name, "foo") + } +} + +func TestIngestRepo_UnknownRowSkipped(t *testing.T) { + ops := []string{jmodels.CommitOperationUpdate, jmodels.CommitOperationDelete} + for _, op := range ops { + t.Run(op, func(t *testing.T) { + ing, _ := newTestIngester(t) + + var e *jmodels.Event + switch op { + case jmodels.CommitOperationUpdate: + e = makeEvent(t, op, "did:plc:nobody", "ghost", tangled.Repo{ + Knot: "knot.example", + Name: ptr("ghost"), + RepoDid: ptr("did:plc:nope"), + }) + case jmodels.CommitOperationDelete: + e = makeDeleteEvent("did:plc:nobody", "ghost") + } + + if err := ing.ingestRepo(context.Background(), e); err != nil { + t.Fatalf("ingestRepo: %v", err) + } + }) + } +} + +func TestIngestRepo_UpdateNoRepoDidSkipped(t *testing.T) { + ing, _ := newTestIngester(t) + seedRepoRow(t, ing, "did:plc:akshay", "knot.example", "foo", "foo", "did:plc:repo1") + + e := makeEvent(t, jmodels.CommitOperationUpdate, "did:plc:akshay", "foo", tangled.Repo{ + Knot: "knot.example", + Name: ptr("bar"), + }) + + if err := ing.ingestRepo(context.Background(), e); err != nil { + t.Fatalf("ingestRepo: %v", err) + } + + r := loadRepo(t, ing, "did:plc:akshay", "foo") + if r.Name != "foo" { + t.Errorf("name = %q, want unchanged %q", r.Name, "foo") + } +} + +func TestIngestRepo_DeleteRemovesRow(t *testing.T) { + ing, _ := newTestIngester(t) + seedRepoRow(t, ing, "did:plc:akshay", "knot.example", "foo", "foo", "did:plc:repo1") + + e := makeDeleteEvent("did:plc:akshay", "foo") + if err := ing.ingestRepo(context.Background(), e); err != nil { + t.Fatalf("ingestRepo: %v", err) + } + + _, err := db.GetRepo(ing.Db, + orm.FilterEq("did", "did:plc:akshay"), + orm.FilterEq("rkey", "foo"), + ) + if !errors.Is(err, sql.ErrNoRows) { + t.Errorf("expected row to be deleted, got err = %v", err) + } +} + +func TestIngestRepo_MalformedRecord(t *testing.T) { + ing, _ := newTestIngester(t) + + e := &jmodels.Event{ + Did: "did:plc:akshay", + Kind: jmodels.EventKindCommit, + Commit: &jmodels.Commit{ + Operation: jmodels.CommitOperationUpdate, + Collection: tangled.RepoNSID, + RKey: "rkey1", + Record: json.RawMessage("{not json"), + }, + } + + if err := ing.ingestRepo(context.Background(), e); err == nil { + t.Errorf("ingestRepo with malformed record: err = nil, want error") + } +} + +func TestIngestRepo_RenameDeleteSequenceNoTornState(t *testing.T) { + ing, spy := newTestIngester(t) + seedRepoRow(t, ing, "did:plc:akshay", "knot.example", "oldname", "oldname", "did:plc:repo1") + + if _, err := ing.Db.Exec( + `insert into stars (did, rkey, subject_type, subject) values (?, ?, ?, ?)`, + "did:plc:boltless", "star1", "repo", "did:plc:repo1", + ); err != nil { + t.Fatalf("seed star: %v", err) + } + + createEvt := makeEvent(t, jmodels.CommitOperationCreate, "did:plc:akshay", "newname", tangled.Repo{ + Knot: "knot.example", + Name: ptr("NewName"), + RepoDid: ptr("did:plc:repo1"), + }) + if err := ing.ingestRepo(context.Background(), createEvt); err != nil { + t.Fatalf("ingest create: %v", err) + } + + deleteEvt := makeDeleteEvent("did:plc:akshay", "oldname") + if err := ing.ingestRepo(context.Background(), deleteEvt); err != nil { + t.Fatalf("ingest delete: %v", err) + } + + r := loadRepo(t, ing, "did:plc:akshay", "newname") + if r.Name != "NewName" { + t.Errorf("name = %q, want %q", r.Name, "NewName") + } + if r.RepoDid != "did:plc:repo1" { + t.Errorf("repoDid = %q, want %q", r.RepoDid, "did:plc:repo1") + } + + _, err := db.GetRepo(ing.Db, + orm.FilterEq("did", "did:plc:akshay"), + orm.FilterEq("rkey", "oldname"), + ) + if !errors.Is(err, sql.ErrNoRows) { + t.Errorf("old rkey should be gone, got err = %v", err) + } + + var starSubject string + if err := ing.Db.QueryRow(`select subject from stars where did = ?`, "did:plc:boltless").Scan(&starSubject); err != nil { + t.Fatalf("query star: %v", err) + } + if starSubject != "did:plc:repo1" { + t.Errorf("star subject = %q, want %q", starSubject, "did:plc:repo1") + } + + if spy.renames != 1 { + t.Errorf("RenameRepo called %d times, want 1", spy.renames) + } + if spy.creates != 0 { + t.Errorf("rename should not create: NewRepo called %d times, want 0", spy.creates) + } + if spy.deletes != 0 { + t.Errorf("old rkey already gone, DeleteRepo should not be called but was called %d times", spy.deletes) + } +} + +func TestIngestRepo_CreateFallsBackToRkeyForName(t *testing.T) { + ing, _ := newTestIngester(t) + + e := makeEvent(t, jmodels.CommitOperationCreate, "did:plc:akshay", "myrepo", tangled.Repo{ + Knot: "knot.example", + RepoDid: ptr("did:plc:repo1"), + }) + + if err := ing.ingestRepo(context.Background(), e); err != nil { + t.Fatalf("ingestRepo: %v", err) + } + + r := loadRepo(t, ing, "did:plc:akshay", "myrepo") + if r.Name != "myrepo" { + t.Errorf("name should fall back to rkey: got %q, want %q", r.Name, "myrepo") + } +} diff --git a/appview/indexer/notifier.go b/appview/indexer/notifier.go --- a/appview/indexer/notifier.go +++ b/appview/indexer/notifier.go @@ -4,7 +4,6 @@ "context" "github.com/bluesky-social/indigo/atproto/syntax" - "tangled.org/core/api/tangled" "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/appview/notify" @@ -14,10 +13,10 @@ var _ notify.Notifier = &Indexer{} -func (ix *Indexer) getAndReindexRepo(ctx context.Context, repoAt syntax.ATURI) { - l := log.FromContext(ctx).With("notifier", "indexer", "repo_at", repoAt) +func (ix *Indexer) getAndReindexRepo(ctx context.Context, repoDid syntax.DID) { + l := log.FromContext(ctx).With("notifier", "indexer", "repo_did", repoDid) - repo, err := db.GetRepo(ix.Db, orm.FilterEq("at_uri", repoAt.String())) + repo, err := db.GetRepo(ix.Db, orm.FilterEq("repo_did", string(repoDid))) if err != nil { l.Error("failed to get repo for reindexing", "err", err) return @@ -39,7 +38,7 @@ } l.Debug("reindexing repo after new issue") - ix.getAndReindexRepo(ctx, issue.RepoAt) + ix.getAndReindexRepo(ctx, issue.RepoDid) } func (ix *Indexer) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) { @@ -61,7 +60,7 @@ } l.Debug("reindexing repo after issue deletion") - ix.getAndReindexRepo(ctx, issue.RepoAt) + ix.getAndReindexRepo(ctx, issue.RepoDid) } func (ix *Indexer) NewIssueLabelOp(ctx context.Context, issue *models.Issue) { @@ -92,7 +91,7 @@ } l.Debug("reindexing repo after new pull") - ix.getAndReindexRepo(ctx, pull.RepoAt) + ix.getAndReindexRepo(ctx, pull.RepoDid) } func (ix *Indexer) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) { @@ -113,6 +112,15 @@ } } +func (ix *Indexer) RenameRepo(ctx context.Context, actor syntax.DID, oldRepo, newRepo *models.Repo) { + l := log.FromContext(ctx).With("notifier", "indexer", "repo", newRepo.RepoIdentifier(), "actor", actor, "old_name", oldRepo.Name, "new_name", newRepo.Name) + l.Debug("reindexing repo after rename") + err := ix.Repos.Index(ctx, *newRepo) + if err != nil { + l.Error("failed to reindex repo", "err", err) + } +} + func (ix *Indexer) DeleteRepo(ctx context.Context, repo *models.Repo) { l := log.FromContext(ctx).With("notifier", "indexer", "repo", repo) l.Debug("deleting repo from index") @@ -125,21 +133,21 @@ func (ix *Indexer) NewStar(ctx context.Context, star *models.Star) { l := log.FromContext(ctx).With("notifier", "indexer", "star", star) - if star.RepoAt.Collection().String() != tangled.RepoNSID { + if star.SubjectType != models.StarSubjectRepo { return } l.Debug("reindexing repo after new star") - ix.getAndReindexRepo(ctx, star.RepoAt) + ix.getAndReindexRepo(ctx, syntax.DID(star.Subject)) } func (ix *Indexer) DeleteStar(ctx context.Context, star *models.Star) { l := log.FromContext(ctx).With("notifier", "indexer", "star", star) - if star.RepoAt.Collection().String() != tangled.RepoNSID { + if star.SubjectType != models.StarSubjectRepo { return } l.Debug("reindexing repo after star deletion") - ix.getAndReindexRepo(ctx, star.RepoAt) + ix.getAndReindexRepo(ctx, syntax.DID(star.Subject)) } diff --git a/appview/indexer/issues/indexer.go b/appview/indexer/issues/indexer.go --- a/appview/indexer/issues/indexer.go +++ b/appview/indexer/issues/indexer.go @@ -31,7 +31,7 @@ unicodeNormalizeName = "uicodeNormalize" // Bump this when the index mapping changes to trigger a rebuild. - issueIndexerVersion = 3 + issueIndexerVersion = 4 ) type Indexer struct { @@ -85,7 +85,7 @@ docMapping.AddFieldMappingsAt("title", textFieldMapping) docMapping.AddFieldMappingsAt("body", textFieldMapping) - docMapping.AddFieldMappingsAt("repo_at", keywordFieldMapping) + docMapping.AddFieldMappingsAt("repo_did", keywordFieldMapping) docMapping.AddFieldMappingsAt("is_open", boolFieldMapping) docMapping.AddFieldMappingsAt("author_did", keywordFieldMapping) docMapping.AddFieldMappingsAt("labels", keywordFieldMapping) @@ -185,7 +185,7 @@ type issueData struct { ID int64 `json:"id"` - RepoAt string `json:"repo_at"` + RepoDid string `json:"repo_did"` IssueID int `json:"issue_id"` Title string `json:"title"` Body string `json:"body"` @@ -200,7 +200,7 @@ func makeIssueData(issue *models.Issue) *issueData { return &issueData{ ID: issue.Id, - RepoAt: issue.RepoAt.String(), + RepoDid: string(issue.RepoDid), IssueID: issue.IssueId, Title: issue.Title, Body: issue.Body, @@ -274,7 +274,7 @@ )) } - musts = append(musts, bleveutil.KeywordFieldQuery("repo_at", opts.RepoAt)) + musts = append(musts, bleveutil.KeywordFieldQuery("repo_did", opts.RepoDid)) if opts.IsOpen != nil { musts = append(musts, bleveutil.BoolFieldQuery("is_open", *opts.IsOpen)) } diff --git a/appview/indexer/issues/indexer_test.go b/appview/indexer/issues/indexer_test.go --- a/appview/indexer/issues/indexer_test.go +++ b/appview/indexer/issues/indexer_test.go @@ -67,17 +67,17 @@ ctx := context.Background() err := ix.Index(ctx, - models.Issue{Id: 1, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Fix login bug", Body: "Users cannot login", Open: true, Did: "did:plc:alice", Labels: makeLabelState("bug")}, - models.Issue{Id: 2, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Add dark mode", Body: "Implement dark theme", Open: true, Did: "did:plc:bob", Labels: makeLabelState("feature")}, - models.Issue{Id: 3, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Fix login timeout", Body: "Login takes too long", Open: false, Did: "did:plc:alice", Labels: makeLabelState("bug")}, + models.Issue{Id: 1, RepoDid: "did:plc:testrepo", Title: "Fix login bug", Body: "Users cannot login", Open: true, Did: "did:plc:alice", Labels: makeLabelState("bug")}, + models.Issue{Id: 2, RepoDid: "did:plc:testrepo", Title: "Add dark mode", Body: "Implement dark theme", Open: true, Did: "did:plc:bob", Labels: makeLabelState("feature")}, + models.Issue{Id: 3, RepoDid: "did:plc:testrepo", Title: "Fix login timeout", Body: "Login takes too long", Open: false, Did: "did:plc:alice", Labels: makeLabelState("bug")}, ) require.NoError(t, err) opts := func() models.IssueSearchOptions { return models.IssueSearchOptions{ - RepoAt: "at://did:plc:test/sh.tangled.repo/abc", - IsOpen: boolPtr(true), - Page: pagination.Page{Limit: 10}, + RepoDid: "did:plc:testrepo", + IsOpen: boolPtr(true), + Page: pagination.Page{Limit: 10}, } } @@ -148,16 +148,16 @@ ctx := context.Background() err := ix.Index(ctx, - models.Issue{Id: 1, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Issue 1", Body: "Body", Open: true, Did: "did:plc:alice", Labels: makeLabelState("bug")}, - models.Issue{Id: 2, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Issue 2", Body: "Body", Open: true, Did: "did:plc:bob", Labels: makeLabelState("bug", "urgent")}, + models.Issue{Id: 1, RepoDid: "did:plc:testrepo", Title: "Issue 1", Body: "Body", Open: true, Did: "did:plc:alice", Labels: makeLabelState("bug")}, + models.Issue{Id: 2, RepoDid: "did:plc:testrepo", Title: "Issue 2", Body: "Body", Open: true, Did: "did:plc:bob", Labels: makeLabelState("bug", "urgent")}, ) require.NoError(t, err) result, err := ix.Search(ctx, models.IssueSearchOptions{ - RepoAt: "at://did:plc:test/sh.tangled.repo/abc", - IsOpen: boolPtr(true), - Labels: []string{"bug", "urgent"}, - Page: pagination.Page{Limit: 10}, + RepoDid: "did:plc:testrepo", + IsOpen: boolPtr(true), + Labels: []string{"bug", "urgent"}, + Page: pagination.Page{Limit: 10}, }) require.NoError(t, err) assert.Equal(t, uint64(1), result.Total) @@ -171,17 +171,17 @@ ctx := context.Background() err := ix.Index(ctx, - models.Issue{Id: 1, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Fix login bug", Body: "Users cannot login", Open: true, Did: "did:plc:alice", Labels: makeLabelState("bug")}, - models.Issue{Id: 2, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Add dark mode", Body: "Implement dark theme", Open: true, Did: "did:plc:bob", Labels: makeLabelState("feature")}, - models.Issue{Id: 3, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Fix timeout bug", Body: "Timeout on save", Open: true, Did: "did:plc:alice", Labels: makeLabelState("bug", "urgent")}, + models.Issue{Id: 1, RepoDid: "did:plc:testrepo", Title: "Fix login bug", Body: "Users cannot login", Open: true, Did: "did:plc:alice", Labels: makeLabelState("bug")}, + models.Issue{Id: 2, RepoDid: "did:plc:testrepo", Title: "Add dark mode", Body: "Implement dark theme", Open: true, Did: "did:plc:bob", Labels: makeLabelState("feature")}, + models.Issue{Id: 3, RepoDid: "did:plc:testrepo", Title: "Fix timeout bug", Body: "Timeout on save", Open: true, Did: "did:plc:alice", Labels: makeLabelState("bug", "urgent")}, ) require.NoError(t, err) opts := func() models.IssueSearchOptions { return models.IssueSearchOptions{ - RepoAt: "at://did:plc:test/sh.tangled.repo/abc", - IsOpen: boolPtr(true), - Page: pagination.Page{Limit: 10}, + RepoDid: "did:plc:testrepo", + IsOpen: boolPtr(true), + Page: pagination.Page{Limit: 10}, } } @@ -227,9 +227,9 @@ ctx := context.Background() err := ix.Index(ctx, - models.Issue{Id: 1, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Fix login bug", Body: "Users cannot login", Open: true, Did: "did:plc:alice"}, - models.Issue{Id: 2, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Add dark mode", Body: "Implement dark theme", Open: true, Did: "did:plc:bob"}, - models.Issue{Id: 3, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Fix timeout bug", Body: "Timeout on save", Open: true, Did: "did:plc:alice"}, + models.Issue{Id: 1, RepoDid: "did:plc:testrepo", Title: "Fix login bug", Body: "Users cannot login", Open: true, Did: "did:plc:alice"}, + models.Issue{Id: 2, RepoDid: "did:plc:testrepo", Title: "Add dark mode", Body: "Implement dark theme", Open: true, Did: "did:plc:bob"}, + models.Issue{Id: 3, RepoDid: "did:plc:testrepo", Title: "Fix timeout bug", Body: "Timeout on save", Open: true, Did: "did:plc:alice"}, ) require.NoError(t, err) @@ -244,7 +244,7 @@ require.Equal(t, []string{"dark theme"}, negatedPhrases) result, err := ix.Search(ctx, models.IssueSearchOptions{ - RepoAt: "at://did:plc:test/sh.tangled.repo/abc", + RepoDid: "did:plc:testrepo", IsOpen: boolPtr(true), NegatedPhrases: negatedPhrases, Page: pagination.Page{Limit: 10}, @@ -261,13 +261,13 @@ ctx := context.Background() err := ix.Index(ctx, - models.Issue{Id: 1, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Issue", Body: "Body", Open: true, Did: "did:plc:alice"}, + models.Issue{Id: 1, RepoDid: "did:plc:testrepo", Title: "Issue", Body: "Body", Open: true, Did: "did:plc:alice"}, ) require.NoError(t, err) result, err := ix.Search(ctx, models.IssueSearchOptions{ Keywords: []string{"nonexistent"}, - RepoAt: "at://did:plc:test/sh.tangled.repo/abc", + RepoDid: "did:plc:testrepo", IsOpen: boolPtr(true), Page: pagination.Page{Limit: 10}, }) @@ -283,20 +283,20 @@ ctx := context.Background() err := ix.Index(ctx, - models.Issue{Id: 1, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "High priority bug", Body: "Urgent", Open: true, Did: "did:plc:alice", + models.Issue{Id: 1, RepoDid: "did:plc:testrepo", Title: "High priority bug", Body: "Urgent", Open: true, Did: "did:plc:alice", Labels: makeLabelState("bug", "priority=high")}, - models.Issue{Id: 2, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "Low priority feature", Body: "Nice to have", Open: true, Did: "did:plc:bob", + models.Issue{Id: 2, RepoDid: "did:plc:testrepo", Title: "Low priority feature", Body: "Nice to have", Open: true, Did: "did:plc:bob", Labels: makeLabelState("feature", "priority=low")}, - models.Issue{Id: 3, RepoAt: "at://did:plc:test/sh.tangled.repo/abc", Title: "High priority feature", Body: "Important", Open: true, Did: "did:plc:alice", + models.Issue{Id: 3, RepoDid: "did:plc:testrepo", Title: "High priority feature", Body: "Important", Open: true, Did: "did:plc:alice", Labels: makeLabelState("feature", "priority=high")}, ) require.NoError(t, err) opts := func() models.IssueSearchOptions { return models.IssueSearchOptions{ - RepoAt: "at://did:plc:test/sh.tangled.repo/abc", - IsOpen: boolPtr(true), - Page: pagination.Page{Limit: 10}, + RepoDid: "did:plc:testrepo", + IsOpen: boolPtr(true), + Page: pagination.Page{Limit: 10}, } } diff --git a/appview/indexer/pulls/indexer.go b/appview/indexer/pulls/indexer.go --- a/appview/indexer/pulls/indexer.go +++ b/appview/indexer/pulls/indexer.go @@ -30,7 +30,7 @@ unicodeNormalizeName = "uicodeNormalize" // Bump this when the index mapping changes to trigger a rebuild. - pullIndexerVersion = 3 + pullIndexerVersion = 4 ) type Indexer struct { @@ -80,7 +80,7 @@ docMapping.AddFieldMappingsAt("title", textFieldMapping) docMapping.AddFieldMappingsAt("body", textFieldMapping) - docMapping.AddFieldMappingsAt("repo_at", keywordFieldMapping) + docMapping.AddFieldMappingsAt("repo_did", keywordFieldMapping) docMapping.AddFieldMappingsAt("state", keywordFieldMapping) docMapping.AddFieldMappingsAt("author_did", keywordFieldMapping) docMapping.AddFieldMappingsAt("labels", keywordFieldMapping) @@ -180,7 +180,7 @@ type pullData struct { ID int64 `json:"id"` - RepoAt string `json:"repo_at"` + RepoDid string `json:"repo_did"` PullID int `json:"pull_id"` Title string `json:"title"` Body string `json:"body"` @@ -195,7 +195,7 @@ func makePullData(pull *models.Pull) *pullData { return &pullData{ ID: int64(pull.ID), - RepoAt: pull.RepoAt.String(), + RepoDid: string(pull.RepoDid), PullID: pull.PullId, Title: pull.Title, Body: pull.Body, @@ -275,7 +275,7 @@ )) } - musts = append(musts, bleveutil.KeywordFieldQuery("repo_at", opts.RepoAt)) + musts = append(musts, bleveutil.KeywordFieldQuery("repo_did", opts.RepoDid)) if opts.State != nil { musts = append(musts, bleveutil.KeywordFieldQuery("state", opts.State.String())) } diff --git a/appview/indexer/repos/indexer.go b/appview/indexer/repos/indexer.go --- a/appview/indexer/repos/indexer.go +++ b/appview/indexer/repos/indexer.go @@ -34,7 +34,7 @@ unicodeNormalizeName = "unicodeNormalize" // Bump this when the index mapping changes to trigger a rebuild. - repoIndexerVersion = 6 + repoIndexerVersion = 7 ) type Indexer struct { @@ -120,7 +120,7 @@ docMapping.AddFieldMappingsAt("topics_exact", caseInsensitiveKeywordMapping) docMapping.AddFieldMappingsAt("did", keywordFieldMapping) docMapping.AddFieldMappingsAt("knot", keywordFieldMapping) - docMapping.AddFieldMappingsAt("repo_at", keywordFieldMapping) + docMapping.AddFieldMappingsAt("repo_did", keywordFieldMapping) // fork indicator for down-ranking docMapping.AddFieldMappingsAt("is_fork", booleanFieldMapping) @@ -258,7 +258,7 @@ type repoData struct { ID int64 `json:"id"` - RepoAt string `json:"repo_at"` + RepoDid string `json:"repo_did"` Did string `json:"did"` Name string `json:"name"` NameTrigram string `json:"name_trigram"` @@ -294,7 +294,7 @@ return &repoData{ ID: repo.Id, - RepoAt: repo.RepoAt().String(), + RepoDid: repo.RepoDid, Did: repo.Did, Name: repo.Name, NameTrigram: repo.Name, -- tangled.sh