package models import ( "fmt" "time" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/api/tangled" ) type Pull2 struct { // TODO: PullId is not atprotated yet PullId int Did syntax.DID Rkey syntax.RecordKey Submissions []*PullRound // PR submission history // backlinked records State PullState Labels LabelState Comments []Comment // optionally, populate these when querying for reverse mappings Repo *Repo } // PullRound represents snapshot of `sh.tangled.repo.pull` record // NOTE: Non-patch change can make new submission type PullRound struct { Id int64 Round int Did syntax.DID Rkey syntax.RecordKey Cid syntax.CID // content Target PullTarget Source *PullSource2 // optional for patch based PR Patch string // list of commits encoded in patch object Title string Body string Mentions []syntax.DID References []syntax.ATURI Created time.Time } type PullTarget struct { RepoAt syntax.ATURI Branch string } // PullSource2 is not a source of truth but rather a metadata to help resubmitting type PullSource2 struct { RepoAt syntax.ATURI Branch string } func (p Pull2) AsRecord() tangled.RepoPull { return p.Latest().AsRecord() } func (p *Pull2) AtUri() syntax.ATURI { return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", p.Did, tangled.RepoPullNSID, p.Rkey)) } func (p *Pull2) Latest() *PullRound { return p.Submissions[len(p.Submissions)-1] } func (p *Pull2) CID() syntax.CID { return p.Latest().Cid } func (p *Pull2) Created() time.Time { return p.Submissions[0].Created } func (p *Pull2) Edited() time.Time { return p.Latest().Created } func (p *Pull2) RepoAt() syntax.ATURI { return p.Latest().Target.RepoAt } func (p *Pull2) Validate() error { if len(p.Submissions) == 0 { return fmt.Errorf("Pull should include at least one submission") } // we don't need to validate existing records if err := p.Latest().Validate(); err != nil { return fmt.Errorf("validate latest stack: %w", err) } return nil } func PullContentFromRecord(cid syntax.CID, record tangled.RepoPull) PullRound { var source *PullSource2 if record.Source != nil { source = &PullSource2{} source.Branch = record.Source.Branch if record.Source.Repo != nil { source.RepoAt = syntax.ATURI(*record.Source.Repo) } else { source.RepoAt = syntax.ATURI(record.Target.Repo) } } var ( body string mentions = make([]syntax.DID, len(record.Mentions)) references = make([]syntax.ATURI, len(record.References)) ) if record.Body != nil { body = *record.Body } for i, v := range record.Mentions { mentions[i] = syntax.DID(v) } for i, v := range record.References { references[i] = syntax.ATURI(v) } created, err := time.Parse(record.CreatedAt, time.RFC3339) if err != nil { created = time.Now() } return PullRound{ Cid: cid, Target: PullTarget{ RepoAt: syntax.ATURI(record.Target.Repo), Branch: record.Target.Branch, }, Source: source, Patch: record.Patch, Title: record.Title, Body: body, Created: created, } } // NOTE: AsRecord doesn't include the patch blob id in returned atproto record func (p PullRound) AsRecord() tangled.RepoPull { var source *tangled.RepoPull_Source if p.Source != nil { repoAt := p.Source.RepoAt.String() source = &tangled.RepoPull_Source{ Repo: &repoAt, Branch: p.Source.Branch, } } var body *string if p.Body != "" { body = &p.Body } return tangled.RepoPull{ Target: &tangled.RepoPull_Target{ Repo: p.Target.RepoAt.String(), Branch: p.Target.Branch, }, Source: source, Title: p.Title, Body: body, Mentions: toStringList(p.Mentions), References: toStringList(p.References), } } func (p *PullRound) AtUri() syntax.ATURI { return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", p.Did, tangled.RepoPullNSID, p.Rkey)) } func (p *PullRound) Validate() error { // TODO: validate patches return nil } func toStringList[T fmt.Stringer](list []T) []string { slist := make([]string, len(list)) for i, v := range list { slist[i] = v.String() } return slist }