From f1bdf70b4e6525622f50983ad974e4c4c9b98b87 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Wed, 20 Aug 2025 16:37:59 +0000 Subject: [PATCH] appview: ingest issue records Signed-off-by: Anirudh Oppiliappan --- appview/ingester.go | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ appview/state/state.go | 1 + 2 file(s) changed, 100 insertion(s)(+), 6 deletion(s)(-) diff --git a/appview/ingester.go b/appview/ingester.go --- a/appview/ingester.go +++ b/appview/ingester.go @@ -5,6 +5,7 @@ "context" "encoding/json" "fmt" "log/slog" + "strings" "time" "github.com/bluesky-social/indigo/atproto/syntax" @@ -14,6 +15,7 @@ "github.com/ipfs/go-cid" "tangled.sh/tangled.sh/core/api/tangled" "tangled.sh/tangled.sh/core/appview/config" "tangled.sh/tangled.sh/core/appview/db" + "tangled.sh/tangled.sh/core/appview/pages/markup" "tangled.sh/tangled.sh/core/appview/serververify" "tangled.sh/tangled.sh/core/idresolver" "tangled.sh/tangled.sh/core/rbac" @@ -61,15 +63,17 @@ err = i.ingestArtifact(e) case tangled.ActorProfileNSID: err = i.ingestProfile(e) case tangled.SpindleMemberNSID: - err = i.ingestSpindleMember(e) + err = i.ingestSpindleMember(ctx, e) case tangled.SpindleNSID: - err = i.ingestSpindle(e) + err = i.ingestSpindle(ctx, e) case tangled.KnotMemberNSID: err = i.ingestKnotMember(e) case tangled.KnotNSID: err = i.ingestKnot(e) case tangled.StringNSID: err = i.ingestString(e) + case tangled.RepoIssueNSID: + err = i.ingestIssue(ctx, e) } l = i.Logger.With("nsid", e.Commit.Collection) } @@ -340,7 +344,7 @@ return nil } -func (i *Ingester) ingestSpindleMember(e *models.Event) error { +func (i *Ingester) ingestSpindleMember(ctx context.Context, e *models.Event) error { did := e.Did var err error @@ -363,7 +367,7 @@ if err != nil || !ok { return fmt.Errorf("failed to enforce permissions: %w", err) } - memberId, err := i.IdResolver.ResolveIdent(context.Background(), record.Subject) + memberId, err := i.IdResolver.ResolveIdent(ctx, record.Subject) if err != nil { return err } @@ -446,7 +450,7 @@ return nil } -func (i *Ingester) ingestSpindle(e *models.Event) error { +func (i *Ingester) ingestSpindle(ctx context.Context, e *models.Event) error { did := e.Did var err error @@ -479,7 +483,7 @@ l.Error("failed to add spindle to db", "err", err, "instance", instance) return err } - err = serververify.RunVerification(context.Background(), instance, did, i.Config.Core.Dev) + err = serververify.RunVerification(ctx, instance, did, i.Config.Core.Dev) if err != nil { l.Error("failed to add spindle to db", "err", err, "instance", instance) return err @@ -769,3 +773,92 @@ } return nil } +func (i *Ingester) ingestIssue(ctx context.Context, e *models.Event) error { + did := e.Did + rkey := e.Commit.RKey + + var err error + + l := i.Logger.With("handler", "ingestIssue", "nsid", e.Commit.Collection, "did", did, "rkey", rkey) + l.Info("ingesting record") + + ddb, ok := i.Db.Execer.(*db.DB) + if !ok { + return fmt.Errorf("failed to index issue record, invalid db cast") + } + + switch e.Commit.Operation { + case models.CommitOperationCreate: + raw := json.RawMessage(e.Commit.Record) + record := tangled.RepoIssue{} + err = json.Unmarshal(raw, &record) + if err != nil { + l.Error("invalid record", "err", err) + return err + } + + issue := db.IssueFromRecord(did, rkey, record) + + sanitizer := markup.NewSanitizer() + if st := strings.TrimSpace(sanitizer.SanitizeDescription(issue.Title)); st == "" { + return fmt.Errorf("title is empty after HTML sanitization") + } + if sb := strings.TrimSpace(sanitizer.SanitizeDefault(issue.Body)); sb == "" { + return fmt.Errorf("body is empty after HTML sanitization") + } + + tx, err := ddb.BeginTx(ctx, nil) + if err != nil { + l.Error("failed to begin transaction", "err", err) + return err + } + + err = db.NewIssue(tx, &issue) + if err != nil { + l.Error("failed to create issue", "err", err) + return err + } + + return nil + + case models.CommitOperationUpdate: + raw := json.RawMessage(e.Commit.Record) + record := tangled.RepoIssue{} + err = json.Unmarshal(raw, &record) + if err != nil { + l.Error("invalid record", "err", err) + return err + } + + body := "" + if record.Body != nil { + body = *record.Body + } + + sanitizer := markup.NewSanitizer() + if st := strings.TrimSpace(sanitizer.SanitizeDescription(record.Title)); st == "" { + return fmt.Errorf("title is empty after HTML sanitization") + } + if sb := strings.TrimSpace(sanitizer.SanitizeDefault(body)); sb == "" { + return fmt.Errorf("body is empty after HTML sanitization") + } + + err = db.UpdateIssueByRkey(ddb, did, rkey, record.Title, body) + if err != nil { + l.Error("failed to update issue", "err", err) + return err + } + + return nil + + case models.CommitOperationDelete: + if err := db.DeleteIssueByRkey(ddb, did, rkey); err != nil { + l.Error("failed to delete", "err", err) + return fmt.Errorf("failed to delete issue record: %w", err) + } + + return nil + } + + return fmt.Errorf("unknown operation: %s", e.Commit.Operation) +} diff --git a/appview/state/state.go b/appview/state/state.go --- a/appview/state/state.go +++ b/appview/state/state.go @@ -99,6 +99,7 @@ tangled.ActorProfileNSID, tangled.SpindleMemberNSID, tangled.SpindleNSID, tangled.StringNSID, + tangled.RepoIssueNSID, }, nil, slog.Default(), -- tangled.sh