Monorepo for Tangled

appview, knotserver: add dual-mode repo DID addressing

+937 -342
+1 -1
api/tangled/actorprofile.go
··· 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.actor.profile", &ActorProfile{}) 17 } // 18 // RECORDTYPE: ActorProfile 19 type ActorProfile struct {
··· 13 ) 14 15 func init() { 16 + // util.RegisterType("sh.tangled.actor.profile", &ActorProfile{}) 17 } // 18 // RECORDTYPE: ActorProfile 19 type ActorProfile struct {
+127 -11
api/tangled/cbor_gen.go
··· 1169 } 1170 1171 cw := cbg.NewCborWriter(w) 1172 1173 - if _, err := cw.Write([]byte{168}); err != nil { 1174 return err 1175 } 1176 ··· 1279 } 1280 1281 // t.RepoDid (string) (string) 1282 - if len("repoDid") > 1000000 { 1283 - return xerrors.Errorf("Value in field \"repoDid\" was too long") 1284 } 1285 1286 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("repoDid"))); err != nil { 1287 return err 1288 } 1289 - if _, err := cw.WriteString(string("repoDid")); err != nil { 1290 return err 1291 } 1292 1293 - if len(t.RepoDid) > 1000000 { 1294 - return xerrors.Errorf("Value in field t.RepoDid was too long") 1295 } 1296 1297 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.RepoDid))); err != nil { 1298 return err 1299 } 1300 - if _, err := cw.WriteString(string(t.RepoDid)); err != nil { 1301 return err 1302 } 1303 ··· 1458 case "repoDid": 1459 1460 { 1461 sval, err := cbg.ReadStringWithMax(cr, 1000000) 1462 if err != nil { 1463 return err 1464 } 1465 1466 - t.RepoDid = string(sval) 1467 } 1468 // t.RepoName (string) (string) 1469 case "repoName": ··· 5556 } 5557 5558 cw := cbg.NewCborWriter(w) 5559 5560 - if _, err := cw.Write([]byte{164}); err != nil { 5561 return err 5562 } 5563 ··· 5630 return err 5631 } 5632 5633 // t.DefaultBranch (string) (string) 5634 if len("defaultBranch") > 1000000 { 5635 return xerrors.Errorf("Value in field \"defaultBranch\" was too long") ··· 5728 } 5729 5730 t.Repo = string(sval) 5731 } 5732 // t.DefaultBranch (string) (string) 5733 case "defaultBranch":
··· 1169 } 1170 1171 cw := cbg.NewCborWriter(w) 1172 + fieldCount := 9 1173 1174 + if t.RepoDid == nil { 1175 + fieldCount-- 1176 + } 1177 + 1178 + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { 1179 return err 1180 } 1181 ··· 1284 } 1285 1286 // t.RepoDid (string) (string) 1287 + if t.RepoDid != nil { 1288 + 1289 + if len("repoDid") > 1000000 { 1290 + return xerrors.Errorf("Value in field \"repoDid\" was too long") 1291 + } 1292 + 1293 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("repoDid"))); err != nil { 1294 + return err 1295 + } 1296 + if _, err := cw.WriteString(string("repoDid")); err != nil { 1297 + return err 1298 + } 1299 + 1300 + if t.RepoDid == nil { 1301 + if _, err := cw.Write(cbg.CborNull); err != nil { 1302 + return err 1303 + } 1304 + } else { 1305 + if len(*t.RepoDid) > 1000000 { 1306 + return xerrors.Errorf("Value in field t.RepoDid was too long") 1307 + } 1308 + 1309 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.RepoDid))); err != nil { 1310 + return err 1311 + } 1312 + if _, err := cw.WriteString(string(*t.RepoDid)); err != nil { 1313 + return err 1314 + } 1315 + } 1316 } 1317 1318 + // t.OwnerDid (string) (string) 1319 + if len("ownerDid") > 1000000 { 1320 + return xerrors.Errorf("Value in field \"ownerDid\" was too long") 1321 + } 1322 + 1323 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("ownerDid"))); err != nil { 1324 return err 1325 } 1326 + if _, err := cw.WriteString(string("ownerDid")); err != nil { 1327 return err 1328 } 1329 1330 + if len(t.OwnerDid) > 1000000 { 1331 + return xerrors.Errorf("Value in field t.OwnerDid was too long") 1332 } 1333 1334 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.OwnerDid))); err != nil { 1335 return err 1336 } 1337 + if _, err := cw.WriteString(string(t.OwnerDid)); err != nil { 1338 return err 1339 } 1340 ··· 1495 case "repoDid": 1496 1497 { 1498 + b, err := cr.ReadByte() 1499 + if err != nil { 1500 + return err 1501 + } 1502 + if b != cbg.CborNull[0] { 1503 + if err := cr.UnreadByte(); err != nil { 1504 + return err 1505 + } 1506 + 1507 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 1508 + if err != nil { 1509 + return err 1510 + } 1511 + 1512 + t.RepoDid = (*string)(&sval) 1513 + } 1514 + } 1515 + // t.OwnerDid (string) (string) 1516 + case "ownerDid": 1517 + 1518 + { 1519 sval, err := cbg.ReadStringWithMax(cr, 1000000) 1520 if err != nil { 1521 return err 1522 } 1523 1524 + t.OwnerDid = string(sval) 1525 } 1526 // t.RepoName (string) (string) 1527 case "repoName": ··· 5614 } 5615 5616 cw := cbg.NewCborWriter(w) 5617 + fieldCount := 5 5618 5619 + if t.RepoDid == nil { 5620 + fieldCount-- 5621 + } 5622 + 5623 + if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil { 5624 return err 5625 } 5626 ··· 5693 return err 5694 } 5695 5696 + // t.RepoDid (string) (string) 5697 + if t.RepoDid != nil { 5698 + 5699 + if len("repoDid") > 1000000 { 5700 + return xerrors.Errorf("Value in field \"repoDid\" was too long") 5701 + } 5702 + 5703 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("repoDid"))); err != nil { 5704 + return err 5705 + } 5706 + if _, err := cw.WriteString(string("repoDid")); err != nil { 5707 + return err 5708 + } 5709 + 5710 + if t.RepoDid == nil { 5711 + if _, err := cw.Write(cbg.CborNull); err != nil { 5712 + return err 5713 + } 5714 + } else { 5715 + if len(*t.RepoDid) > 1000000 { 5716 + return xerrors.Errorf("Value in field t.RepoDid was too long") 5717 + } 5718 + 5719 + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.RepoDid))); err != nil { 5720 + return err 5721 + } 5722 + if _, err := cw.WriteString(string(*t.RepoDid)); err != nil { 5723 + return err 5724 + } 5725 + } 5726 + } 5727 + 5728 // t.DefaultBranch (string) (string) 5729 if len("defaultBranch") > 1000000 { 5730 return xerrors.Errorf("Value in field \"defaultBranch\" was too long") ··· 5823 } 5824 5825 t.Repo = string(sval) 5826 + } 5827 + // t.RepoDid (string) (string) 5828 + case "repoDid": 5829 + 5830 + { 5831 + b, err := cr.ReadByte() 5832 + if err != nil { 5833 + return err 5834 + } 5835 + if b != cbg.CborNull[0] { 5836 + if err := cr.UnreadByte(); err != nil { 5837 + return err 5838 + } 5839 + 5840 + sval, err := cbg.ReadStringWithMax(cr, 1000000) 5841 + if err != nil { 5842 + return err 5843 + } 5844 + 5845 + t.RepoDid = (*string)(&sval) 5846 + } 5847 } 5848 // t.DefaultBranch (string) (string) 5849 case "defaultBranch":
+1 -5
api/tangled/feedreaction.go
··· 4 5 // schema: sh.tangled.feed.reaction 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 FeedReactionNSID = "sh.tangled.feed.reaction" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.feed.reaction", &FeedReaction{}) 17 } // 18 // RECORDTYPE: FeedReaction 19 type FeedReaction struct {
··· 4 5 // schema: sh.tangled.feed.reaction 6 7 const ( 8 FeedReactionNSID = "sh.tangled.feed.reaction" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.feed.reaction", &FeedReaction{}) 13 } // 14 // RECORDTYPE: FeedReaction 15 type FeedReaction struct {
+1 -5
api/tangled/feedstar.go
··· 4 5 // schema: sh.tangled.feed.star 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 FeedStarNSID = "sh.tangled.feed.star" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.feed.star", &FeedStar{}) 17 } // 18 // RECORDTYPE: FeedStar 19 type FeedStar struct {
··· 4 5 // schema: sh.tangled.feed.star 6 7 const ( 8 FeedStarNSID = "sh.tangled.feed.star" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.feed.star", &FeedStar{}) 13 } // 14 // RECORDTYPE: FeedStar 15 type FeedStar struct {
+5 -7
api/tangled/gitrefUpdate.go
··· 4 5 // schema: sh.tangled.git.refUpdate 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 GitRefUpdateNSID = "sh.tangled.git.refUpdate" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.git.refUpdate", &GitRefUpdate{}) 17 } // 18 // RECORDTYPE: GitRefUpdate 19 type GitRefUpdate struct { ··· 25 NewSha string `json:"newSha" cborgen:"newSha"` 26 // oldSha: old SHA of this ref 27 OldSha string `json:"oldSha" cborgen:"oldSha"` 28 // ref: Ref being updated 29 Ref string `json:"ref" cborgen:"ref"` 30 - // repoDid: did of the owner of the repo 31 - RepoDid string `json:"repoDid" cborgen:"repoDid"` 32 // repoName: name of the repo 33 RepoName string `json:"repoName" cborgen:"repoName"` 34 }
··· 4 5 // schema: sh.tangled.git.refUpdate 6 7 const ( 8 GitRefUpdateNSID = "sh.tangled.git.refUpdate" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.git.refUpdate", &GitRefUpdate{}) 13 } // 14 // RECORDTYPE: GitRefUpdate 15 type GitRefUpdate struct { ··· 21 NewSha string `json:"newSha" cborgen:"newSha"` 22 // oldSha: old SHA of this ref 23 OldSha string `json:"oldSha" cborgen:"oldSha"` 24 + // ownerDid: did of the owner of the repo 25 + OwnerDid string `json:"ownerDid" cborgen:"ownerDid"` 26 // ref: Ref being updated 27 Ref string `json:"ref" cborgen:"ref"` 28 + // repoDid: DID of the repo itself, if assigned 29 + RepoDid *string `json:"repoDid,omitempty" cborgen:"repoDid,omitempty"` 30 // repoName: name of the repo 31 RepoName string `json:"repoName" cborgen:"repoName"` 32 }
+1 -5
api/tangled/graphfollow.go
··· 4 5 // schema: sh.tangled.graph.follow 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 GraphFollowNSID = "sh.tangled.graph.follow" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.graph.follow", &GraphFollow{}) 17 } // 18 // RECORDTYPE: GraphFollow 19 type GraphFollow struct {
··· 4 5 // schema: sh.tangled.graph.follow 6 7 const ( 8 GraphFollowNSID = "sh.tangled.graph.follow" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.graph.follow", &GraphFollow{}) 13 } // 14 // RECORDTYPE: GraphFollow 15 type GraphFollow struct {
+1 -5
api/tangled/issuecomment.go
··· 4 5 // schema: sh.tangled.repo.issue.comment 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 RepoIssueCommentNSID = "sh.tangled.repo.issue.comment" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.repo.issue.comment", &RepoIssueComment{}) 17 } // 18 // RECORDTYPE: RepoIssueComment 19 type RepoIssueComment struct {
··· 4 5 // schema: sh.tangled.repo.issue.comment 6 7 const ( 8 RepoIssueCommentNSID = "sh.tangled.repo.issue.comment" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.repo.issue.comment", &RepoIssueComment{}) 13 } // 14 // RECORDTYPE: RepoIssueComment 15 type RepoIssueComment struct {
+1 -5
api/tangled/issuestate.go
··· 4 5 // schema: sh.tangled.repo.issue.state 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 RepoIssueStateNSID = "sh.tangled.repo.issue.state" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.repo.issue.state", &RepoIssueState{}) 17 } // 18 // RECORDTYPE: RepoIssueState 19 type RepoIssueState struct {
··· 4 5 // schema: sh.tangled.repo.issue.state 6 7 const ( 8 RepoIssueStateNSID = "sh.tangled.repo.issue.state" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.repo.issue.state", &RepoIssueState{}) 13 } // 14 // RECORDTYPE: RepoIssueState 15 type RepoIssueState struct {
+1 -5
api/tangled/knotmember.go
··· 4 5 // schema: sh.tangled.knot.member 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 KnotMemberNSID = "sh.tangled.knot.member" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.knot.member", &KnotMember{}) 17 } // 18 // RECORDTYPE: KnotMember 19 type KnotMember struct {
··· 4 5 // schema: sh.tangled.knot.member 6 7 const ( 8 KnotMemberNSID = "sh.tangled.knot.member" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.knot.member", &KnotMember{}) 13 } // 14 // RECORDTYPE: KnotMember 15 type KnotMember struct {
+1 -5
api/tangled/labeldefinition.go
··· 4 5 // schema: sh.tangled.label.definition 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 LabelDefinitionNSID = "sh.tangled.label.definition" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.label.definition", &LabelDefinition{}) 17 } // 18 // RECORDTYPE: LabelDefinition 19 type LabelDefinition struct {
··· 4 5 // schema: sh.tangled.label.definition 6 7 const ( 8 LabelDefinitionNSID = "sh.tangled.label.definition" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.label.definition", &LabelDefinition{}) 13 } // 14 // RECORDTYPE: LabelDefinition 15 type LabelDefinition struct {
+1 -5
api/tangled/labelop.go
··· 4 5 // schema: sh.tangled.label.op 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 LabelOpNSID = "sh.tangled.label.op" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.label.op", &LabelOp{}) 17 } // 18 // RECORDTYPE: LabelOp 19 type LabelOp struct {
··· 4 5 // schema: sh.tangled.label.op 6 7 const ( 8 LabelOpNSID = "sh.tangled.label.op" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.label.op", &LabelOp{}) 13 } // 14 // RECORDTYPE: LabelOp 15 type LabelOp struct {
+1 -5
api/tangled/pipelinestatus.go
··· 4 5 // schema: sh.tangled.pipeline.status 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 PipelineStatusNSID = "sh.tangled.pipeline.status" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.pipeline.status", &PipelineStatus{}) 17 } // 18 // RECORDTYPE: PipelineStatus 19 type PipelineStatus struct {
··· 4 5 // schema: sh.tangled.pipeline.status 6 7 const ( 8 PipelineStatusNSID = "sh.tangled.pipeline.status" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.pipeline.status", &PipelineStatus{}) 13 } // 14 // RECORDTYPE: PipelineStatus 15 type PipelineStatus struct {
+1 -5
api/tangled/pullcomment.go
··· 4 5 // schema: sh.tangled.repo.pull.comment 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 RepoPullCommentNSID = "sh.tangled.repo.pull.comment" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.repo.pull.comment", &RepoPullComment{}) 17 } // 18 // RECORDTYPE: RepoPullComment 19 type RepoPullComment struct {
··· 4 5 // schema: sh.tangled.repo.pull.comment 6 7 const ( 8 RepoPullCommentNSID = "sh.tangled.repo.pull.comment" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.repo.pull.comment", &RepoPullComment{}) 13 } // 14 // RECORDTYPE: RepoPullComment 15 type RepoPullComment struct {
+1 -5
api/tangled/pullstatus.go
··· 4 5 // schema: sh.tangled.repo.pull.status 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 RepoPullStatusNSID = "sh.tangled.repo.pull.status" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.repo.pull.status", &RepoPullStatus{}) 17 } // 18 // RECORDTYPE: RepoPullStatus 19 type RepoPullStatus struct {
··· 4 5 // schema: sh.tangled.repo.pull.status 6 7 const ( 8 RepoPullStatusNSID = "sh.tangled.repo.pull.status" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.repo.pull.status", &RepoPullStatus{}) 13 } // 14 // RECORDTYPE: RepoPullStatus 15 type RepoPullStatus struct {
+1 -1
api/tangled/repoartifact.go
··· 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.repo.artifact", &RepoArtifact{}) 17 } // 18 // RECORDTYPE: RepoArtifact 19 type RepoArtifact struct {
··· 13 ) 14 15 func init() { 16 + // util.RegisterType("sh.tangled.repo.artifact", &RepoArtifact{}) 17 } // 18 // RECORDTYPE: RepoArtifact 19 type RepoArtifact struct {
+1 -5
api/tangled/repocollaborator.go
··· 4 5 // schema: sh.tangled.repo.collaborator 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 RepoCollaboratorNSID = "sh.tangled.repo.collaborator" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.repo.collaborator", &RepoCollaborator{}) 17 } // 18 // RECORDTYPE: RepoCollaborator 19 type RepoCollaborator struct {
··· 4 5 // schema: sh.tangled.repo.collaborator 6 7 const ( 8 RepoCollaboratorNSID = "sh.tangled.repo.collaborator" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.repo.collaborator", &RepoCollaborator{}) 13 } // 14 // RECORDTYPE: RepoCollaborator 15 type RepoCollaborator struct {
+1 -5
api/tangled/repoissue.go
··· 4 5 // schema: sh.tangled.repo.issue 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 RepoIssueNSID = "sh.tangled.repo.issue" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.repo.issue", &RepoIssue{}) 17 } // 18 // RECORDTYPE: RepoIssue 19 type RepoIssue struct {
··· 4 5 // schema: sh.tangled.repo.issue 6 7 const ( 8 RepoIssueNSID = "sh.tangled.repo.issue" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.repo.issue", &RepoIssue{}) 13 } // 14 // RECORDTYPE: RepoIssue 15 type RepoIssue struct {
+1 -1
api/tangled/repopull.go
··· 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.repo.pull", &RepoPull{}) 17 } // 18 // RECORDTYPE: RepoPull 19 type RepoPull struct {
··· 13 ) 14 15 func init() { 16 + // util.RegisterType("sh.tangled.repo.pull", &RepoPull{}) 17 } // 18 // RECORDTYPE: RepoPull 19 type RepoPull struct {
+1 -5
api/tangled/spindlemember.go
··· 4 5 // schema: sh.tangled.spindle.member 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 SpindleMemberNSID = "sh.tangled.spindle.member" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.spindle.member", &SpindleMember{}) 17 } // 18 // RECORDTYPE: SpindleMember 19 type SpindleMember struct {
··· 4 5 // schema: sh.tangled.spindle.member 6 7 const ( 8 SpindleMemberNSID = "sh.tangled.spindle.member" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.spindle.member", &SpindleMember{}) 13 } // 14 // RECORDTYPE: SpindleMember 15 type SpindleMember struct {
+1 -5
api/tangled/tangledknot.go
··· 4 5 // schema: sh.tangled.knot 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 KnotNSID = "sh.tangled.knot" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.knot", &Knot{}) 17 } // 18 // RECORDTYPE: Knot 19 type Knot struct {
··· 4 5 // schema: sh.tangled.knot 6 7 const ( 8 KnotNSID = "sh.tangled.knot" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.knot", &Knot{}) 13 } // 14 // RECORDTYPE: Knot 15 type Knot struct {
+3 -5
api/tangled/tangledpipeline.go
··· 4 5 // schema: sh.tangled.pipeline 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 PipelineNSID = "sh.tangled.pipeline" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.pipeline", &Pipeline{}) 17 } // 18 // RECORDTYPE: Pipeline 19 type Pipeline struct { ··· 70 Did string `json:"did" cborgen:"did"` 71 Knot string `json:"knot" cborgen:"knot"` 72 Repo string `json:"repo" cborgen:"repo"` 73 } 74 75 // Pipeline_Workflow is a "workflow" in the sh.tangled.pipeline schema.
··· 4 5 // schema: sh.tangled.pipeline 6 7 const ( 8 PipelineNSID = "sh.tangled.pipeline" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.pipeline", &Pipeline{}) 13 } // 14 // RECORDTYPE: Pipeline 15 type Pipeline struct { ··· 66 Did string `json:"did" cborgen:"did"` 67 Knot string `json:"knot" cborgen:"knot"` 68 Repo string `json:"repo" cborgen:"repo"` 69 + // repoDid: DID of the repo itself, if assigned 70 + RepoDid *string `json:"repoDid,omitempty" cborgen:"repoDid,omitempty"` 71 } 72 73 // Pipeline_Workflow is a "workflow" in the sh.tangled.pipeline schema.
+1 -5
api/tangled/tangledpublicKey.go
··· 4 5 // schema: sh.tangled.publicKey 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 PublicKeyNSID = "sh.tangled.publicKey" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.publicKey", &PublicKey{}) 17 } // 18 // RECORDTYPE: PublicKey 19 type PublicKey struct {
··· 4 5 // schema: sh.tangled.publicKey 6 7 const ( 8 PublicKeyNSID = "sh.tangled.publicKey" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.publicKey", &PublicKey{}) 13 } // 14 // RECORDTYPE: PublicKey 15 type PublicKey struct {
+1 -5
api/tangled/tangledrepo.go
··· 4 5 // schema: sh.tangled.repo 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 RepoNSID = "sh.tangled.repo" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.repo", &Repo{}) 17 } // 18 // RECORDTYPE: Repo 19 type Repo struct {
··· 4 5 // schema: sh.tangled.repo 6 7 const ( 8 RepoNSID = "sh.tangled.repo" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.repo", &Repo{}) 13 } // 14 // RECORDTYPE: Repo 15 type Repo struct {
+1 -5
api/tangled/tangledspindle.go
··· 4 5 // schema: sh.tangled.spindle 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 SpindleNSID = "sh.tangled.spindle" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.spindle", &Spindle{}) 17 } // 18 // RECORDTYPE: Spindle 19 type Spindle struct {
··· 4 5 // schema: sh.tangled.spindle 6 7 const ( 8 SpindleNSID = "sh.tangled.spindle" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.spindle", &Spindle{}) 13 } // 14 // RECORDTYPE: Spindle 15 type Spindle struct {
+1 -5
api/tangled/tangledstring.go
··· 4 5 // schema: sh.tangled.string 6 7 - import ( 8 - "github.com/bluesky-social/indigo/lex/util" 9 - ) 10 - 11 const ( 12 StringNSID = "sh.tangled.string" 13 ) 14 15 func init() { 16 - util.RegisterType("sh.tangled.string", &String{}) 17 } // 18 // RECORDTYPE: String 19 type String struct {
··· 4 5 // schema: sh.tangled.string 6 7 const ( 8 StringNSID = "sh.tangled.string" 9 ) 10 11 func init() { 12 + // util.RegisterType("sh.tangled.string", &String{}) 13 } // 14 // RECORDTYPE: String 15 type String struct {
+14 -1
appview/db/artifact.go
··· 1 package db 2 3 import ( 4 "fmt" 5 "strings" 6 "time" ··· 12 ) 13 14 func AddArtifact(e Execer, artifact models.Artifact) error { 15 _, err := e.Exec( 16 `insert or ignore into artifacts ( 17 did, 18 rkey, 19 repo_at, 20 tag, 21 created, 22 blob_cid, ··· 24 size, 25 mimetype 26 ) 27 - values (?, ?, ?, ?, ?, ?, ?, ?, ?)`, 28 artifact.Did, 29 artifact.Rkey, 30 artifact.RepoAt, 31 artifact.Tag[:], 32 artifact.CreatedAt.Format(time.RFC3339), 33 artifact.BlobCid.String(), ··· 57 did, 58 rkey, 59 repo_at, 60 tag, 61 created, 62 blob_cid, ··· 78 var createdAt string 79 var tag []byte 80 var blobCid string 81 82 if err := rows.Scan( 83 &artifact.Did, 84 &artifact.Rkey, 85 &artifact.RepoAt, 86 &tag, 87 &createdAt, 88 &blobCid, ··· 91 &artifact.MimeType, 92 ); err != nil { 93 return nil, err 94 } 95 96 artifact.CreatedAt, err = time.Parse(time.RFC3339, createdAt)
··· 1 package db 2 3 import ( 4 + "database/sql" 5 "fmt" 6 "strings" 7 "time" ··· 13 ) 14 15 func AddArtifact(e Execer, artifact models.Artifact) error { 16 + var repoDid *string 17 + if artifact.RepoDid != "" { 18 + repoDid = &artifact.RepoDid 19 + } 20 _, err := e.Exec( 21 `insert or ignore into artifacts ( 22 did, 23 rkey, 24 repo_at, 25 + repo_did, 26 tag, 27 created, 28 blob_cid, ··· 30 size, 31 mimetype 32 ) 33 + values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, 34 artifact.Did, 35 artifact.Rkey, 36 artifact.RepoAt, 37 + repoDid, 38 artifact.Tag[:], 39 artifact.CreatedAt.Format(time.RFC3339), 40 artifact.BlobCid.String(), ··· 64 did, 65 rkey, 66 repo_at, 67 + repo_did, 68 tag, 69 created, 70 blob_cid, ··· 86 var createdAt string 87 var tag []byte 88 var blobCid string 89 + var repoDid sql.NullString 90 91 if err := rows.Scan( 92 &artifact.Did, 93 &artifact.Rkey, 94 &artifact.RepoAt, 95 + &repoDid, 96 &tag, 97 &createdAt, 98 &blobCid, ··· 101 &artifact.MimeType, 102 ); err != nil { 103 return nil, err 104 + } 105 + if repoDid.Valid { 106 + artifact.RepoDid = repoDid.String 107 } 108 109 artifact.CreatedAt, err = time.Parse(time.RFC3339, createdAt)
+77
appview/db/db.go
··· 1276 return err 1277 }) 1278 1279 orm.RunMigration(conn, logger, "add-pds-rewrite-status", func(tx *sql.Tx) error { 1280 _, err := tx.Exec(` 1281 create table if not exists pds_rewrite_status (
··· 1276 return err 1277 }) 1278 1279 + conn.ExecContext(ctx, "pragma foreign_keys = off;") 1280 + orm.RunMigration(conn, logger, "add-repo-did-fk-constraints", func(tx *sql.Tx) error { 1281 + _, err := tx.Exec(` 1282 + create table repo_issue_seqs_new ( 1283 + repo_at text primary key, 1284 + next_issue_id integer not null default 1, 1285 + repo_did text, 1286 + foreign key (repo_did) references repos(repo_did) on delete cascade 1287 + ); 1288 + insert into repo_issue_seqs_new select repo_at, next_issue_id, repo_did from repo_issue_seqs; 1289 + drop table repo_issue_seqs; 1290 + alter table repo_issue_seqs_new rename to repo_issue_seqs; 1291 + 1292 + create table repo_pull_seqs_new ( 1293 + repo_at text primary key, 1294 + next_pull_id integer not null default 1, 1295 + repo_did text, 1296 + foreign key (repo_did) references repos(repo_did) on delete cascade 1297 + ); 1298 + insert into repo_pull_seqs_new select repo_at, next_pull_id, repo_did from repo_pull_seqs; 1299 + drop table repo_pull_seqs; 1300 + alter table repo_pull_seqs_new rename to repo_pull_seqs; 1301 + 1302 + create table repo_languages_new ( 1303 + id integer primary key autoincrement, 1304 + repo_at text not null, 1305 + ref text not null, 1306 + is_default_ref integer not null default 0, 1307 + language text not null, 1308 + bytes integer not null check (bytes >= 0), 1309 + repo_did text, 1310 + unique(repo_at, ref, language), 1311 + foreign key (repo_did) references repos(repo_did) on delete cascade 1312 + ); 1313 + insert into repo_languages_new select id, repo_at, ref, is_default_ref, language, bytes, repo_did from repo_languages; 1314 + drop table repo_languages; 1315 + alter table repo_languages_new rename to repo_languages; 1316 + 1317 + create table repo_labels_new ( 1318 + id integer primary key autoincrement, 1319 + repo_at text not null, 1320 + label_at text not null, 1321 + repo_did text, 1322 + unique (repo_at, label_at), 1323 + foreign key (repo_did) references repos(repo_did) on delete cascade 1324 + ); 1325 + insert into repo_labels_new select id, repo_at, label_at, repo_did from repo_labels; 1326 + drop table repo_labels; 1327 + alter table repo_labels_new rename to repo_labels; 1328 + `) 1329 + return err 1330 + }) 1331 + conn.ExecContext(ctx, "pragma foreign_keys = on;") 1332 + 1333 + orm.RunMigration(conn, logger, "add-repo-did-indexes", func(tx *sql.Tx) error { 1334 + _, err := tx.Exec(` 1335 + create index if not exists idx_issues_repo_did on issues(repo_did); 1336 + create index if not exists idx_pulls_repo_did on pulls(repo_did); 1337 + create index if not exists idx_artifacts_repo_did on artifacts(repo_did); 1338 + create index if not exists idx_collaborators_repo_did on collaborators(repo_did); 1339 + create index if not exists idx_pull_comments_repo_did on pull_comments(repo_did); 1340 + create index if not exists idx_stars_subject_did on stars(subject_did); 1341 + `) 1342 + return err 1343 + }) 1344 + 1345 + orm.RunMigration(conn, logger, "add-repo-did-indexes-2", func(tx *sql.Tx) error { 1346 + _, err := tx.Exec(` 1347 + create index if not exists idx_repo_issue_seqs_repo_did on repo_issue_seqs(repo_did); 1348 + create index if not exists idx_repo_pull_seqs_repo_did on repo_pull_seqs(repo_did); 1349 + create index if not exists idx_repo_languages_repo_did on repo_languages(repo_did); 1350 + create index if not exists idx_repo_labels_repo_did on repo_labels(repo_did); 1351 + create index if not exists idx_webhooks_repo_did on webhooks(repo_did); 1352 + `) 1353 + return err 1354 + }) 1355 + 1356 orm.RunMigration(conn, logger, "add-pds-rewrite-status", func(tx *sql.Tx) error { 1357 _, err := tx.Exec(` 1358 create table if not exists pds_rewrite_status (
+28 -10
appview/db/issues.go
··· 17 ) 18 19 func PutIssue(tx *sql.Tx, issue *models.Issue) error { 20 - // ensure sequence exists 21 _, err := tx.Exec(` 22 - insert or ignore into repo_issue_seqs (repo_at, next_issue_id) 23 - values (?, 1) 24 - `, issue.RepoAt) 25 if err != nil { 26 return err 27 } ··· 64 return err 65 } 66 67 // insert new issue 68 row := tx.QueryRow(` 69 - insert into issues (repo_at, did, rkey, issue_id, title, body) 70 - values (?, ?, ?, ?, ?, ?) 71 returning rowid, issue_id 72 - `, issue.RepoAt, issue.Did, issue.Rkey, newIssueId, issue.Title, issue.Body) 73 74 err = row.Scan(&issue.Id, &issue.IssueId) 75 if err != nil { ··· 83 } 84 85 func updateIssue(tx *sql.Tx, issue *models.Issue) error { 86 - // update existing issue 87 _, err := tx.Exec(` 88 update issues 89 - set title = ?, body = ?, edited = ? 90 where did = ? and rkey = ? 91 - `, issue.Title, issue.Body, time.Now().Format(time.RFC3339), issue.Did, issue.Rkey) 92 if err != nil { 93 return err 94 } ··· 133 did, 134 rkey, 135 repo_at, 136 issue_id, 137 title, 138 body, ··· 161 var issue models.Issue 162 var createdAt string 163 var editedAt, deletedAt sql.Null[string] 164 var rowNum int64 165 err := rows.Scan( 166 &issue.Id, 167 &issue.Did, 168 &issue.Rkey, 169 &issue.RepoAt, 170 &issue.IssueId, 171 &issue.Title, 172 &issue.Body, ··· 178 ) 179 if err != nil { 180 return nil, fmt.Errorf("failed to scan issue: %w", err) 181 } 182 183 if t, err := time.Parse(time.RFC3339, createdAt); err == nil {
··· 17 ) 18 19 func PutIssue(tx *sql.Tx, issue *models.Issue) error { 20 + var seqRepoDid *string 21 + if issue.RepoDid != "" { 22 + seqRepoDid = &issue.RepoDid 23 + } 24 _, err := tx.Exec(` 25 + insert into repo_issue_seqs (repo_at, next_issue_id, repo_did) 26 + values (?, 1, ?) 27 + on conflict(repo_at) do update set repo_did = coalesce(excluded.repo_did, repo_did) 28 + `, issue.RepoAt, seqRepoDid) 29 if err != nil { 30 return err 31 } ··· 68 return err 69 } 70 71 + var repoDid *string 72 + if issue.RepoDid != "" { 73 + repoDid = &issue.RepoDid 74 + } 75 + 76 // insert new issue 77 row := tx.QueryRow(` 78 + insert into issues (repo_at, repo_did, did, rkey, issue_id, title, body) 79 + values (?, ?, ?, ?, ?, ?, ?) 80 returning rowid, issue_id 81 + `, issue.RepoAt, repoDid, issue.Did, issue.Rkey, newIssueId, issue.Title, issue.Body) 82 83 err = row.Scan(&issue.Id, &issue.IssueId) 84 if err != nil { ··· 92 } 93 94 func updateIssue(tx *sql.Tx, issue *models.Issue) error { 95 + var repoDid *string 96 + if issue.RepoDid != "" { 97 + repoDid = &issue.RepoDid 98 + } 99 _, err := tx.Exec(` 100 update issues 101 + set title = ?, body = ?, edited = ?, repo_did = coalesce(?, repo_did) 102 where did = ? and rkey = ? 103 + `, issue.Title, issue.Body, time.Now().Format(time.RFC3339), repoDid, issue.Did, issue.Rkey) 104 if err != nil { 105 return err 106 } ··· 145 did, 146 rkey, 147 repo_at, 148 + repo_did, 149 issue_id, 150 title, 151 body, ··· 174 var issue models.Issue 175 var createdAt string 176 var editedAt, deletedAt sql.Null[string] 177 + var nullableRepoDid sql.NullString 178 var rowNum int64 179 err := rows.Scan( 180 &issue.Id, 181 &issue.Did, 182 &issue.Rkey, 183 &issue.RepoAt, 184 + &nullableRepoDid, 185 &issue.IssueId, 186 &issue.Title, 187 &issue.Body, ··· 193 ) 194 if err != nil { 195 return nil, fmt.Errorf("failed to scan issue: %w", err) 196 + } 197 + if nullableRepoDid.Valid { 198 + issue.RepoDid = nullableRepoDid.String 199 } 200 201 if t, err := time.Parse(time.RFC3339, createdAt); err == nil {
+1 -1
appview/db/profile.go
··· 449 query = `select count(id) from repos where did = ?` 450 args = append(args, did) 451 case models.VanityStatStarCount: 452 - query = `select count(id) from stars where subject_at like 'at://' || ? || '%'` 453 args = append(args, did) 454 case models.VanityStatNone: 455 return 0, nil
··· 449 query = `select count(id) from repos where did = ?` 450 args = append(args, did) 451 case models.VanityStatStarCount: 452 + query = `select count(s.id) from stars s join repos r on (s.subject_at = r.at_uri or (s.subject_did is not null and s.subject_did = r.repo_did)) where r.did = ?` 453 args = append(args, did) 454 case models.VanityStatNone: 455 return 0, nil
+36 -12
appview/db/reference.go
··· 60 on r.did = inp.owner_did 61 and r.name = inp.name 62 join issues i 63 - on i.repo_at = r.at_uri 64 and i.issue_id = inp.issue_id 65 left join issue_comments c 66 on inp.comment_id is not null ··· 131 on r.did = inp.owner_did 132 and r.name = inp.name 133 join pulls p 134 - on p.repo_at = r.at_uri 135 and p.pull_id = inp.pull_id 136 left join pull_comments c 137 on inp.comment_id is not null 138 - and c.repo_at = r.at_uri and c.pull_id = p.pull_id 139 and c.id = inp.comment_id 140 `, 141 strings.Join(vals, ","), ··· 316 } 317 rows, err := e.Query( 318 fmt.Sprintf( 319 - `select r.did, r.name, i.issue_id, i.title, i.open 320 from issues i 321 join repos r 322 - on r.at_uri = i.repo_at 323 where (i.did, i.rkey) in (%s)`, 324 strings.Join(vals, ","), 325 ), ··· 351 filter := orm.FilterIn("c.at_uri", aturis) 352 rows, err := e.Query( 353 fmt.Sprintf( 354 - `select r.did, r.name, i.issue_id, c.id, i.title, i.open 355 from issue_comments c 356 join issues i 357 on i.at_uri = c.issue_at 358 join repos r 359 - on r.at_uri = i.repo_at 360 where %s`, 361 filter.Condition(), 362 ), ··· 396 } 397 rows, err := e.Query( 398 fmt.Sprintf( 399 - `select r.did, r.name, p.pull_id, p.title, p.state 400 from pulls p 401 join repos r 402 - on r.at_uri = p.repo_at 403 where (p.owner_did, p.rkey) in (%s)`, 404 strings.Join(vals, ","), 405 ), ··· 431 filter := orm.FilterIn("c.comment_at", aturis) 432 rows, err := e.Query( 433 fmt.Sprintf( 434 - `select r.did, r.name, p.pull_id, c.id, p.title, p.state 435 from repos r 436 join pulls p 437 - on r.at_uri = p.repo_at 438 join pull_comments c 439 - on r.at_uri = c.repo_at and p.pull_id = c.pull_id 440 where %s`, 441 filter.Condition(), 442 ),
··· 60 on r.did = inp.owner_did 61 and r.name = inp.name 62 join issues i 63 + on coalesce( 64 + nullif(i.repo_did, '') = nullif(r.repo_did, ''), 65 + i.repo_at = r.at_uri 66 + ) 67 and i.issue_id = inp.issue_id 68 left join issue_comments c 69 on inp.comment_id is not null ··· 134 on r.did = inp.owner_did 135 and r.name = inp.name 136 join pulls p 137 + on coalesce( 138 + nullif(p.repo_did, '') = nullif(r.repo_did, ''), 139 + p.repo_at = r.at_uri 140 + ) 141 and p.pull_id = inp.pull_id 142 left join pull_comments c 143 on inp.comment_id is not null 144 + and coalesce( 145 + nullif(c.repo_did, '') = nullif(r.repo_did, ''), 146 + c.repo_at = r.at_uri 147 + ) and c.pull_id = p.pull_id 148 and c.id = inp.comment_id 149 `, 150 strings.Join(vals, ","), ··· 325 } 326 rows, err := e.Query( 327 fmt.Sprintf( 328 + `select distinct r.did, r.name, i.issue_id, i.title, i.open 329 from issues i 330 join repos r 331 + on coalesce( 332 + nullif(i.repo_did, '') = nullif(r.repo_did, ''), 333 + i.repo_at = r.at_uri 334 + ) 335 where (i.did, i.rkey) in (%s)`, 336 strings.Join(vals, ","), 337 ), ··· 363 filter := orm.FilterIn("c.at_uri", aturis) 364 rows, err := e.Query( 365 fmt.Sprintf( 366 + `select distinct r.did, r.name, i.issue_id, c.id, i.title, i.open 367 from issue_comments c 368 join issues i 369 on i.at_uri = c.issue_at 370 join repos r 371 + on coalesce( 372 + nullif(i.repo_did, '') = nullif(r.repo_did, ''), 373 + i.repo_at = r.at_uri 374 + ) 375 where %s`, 376 filter.Condition(), 377 ), ··· 411 } 412 rows, err := e.Query( 413 fmt.Sprintf( 414 + `select distinct r.did, r.name, p.pull_id, p.title, p.state 415 from pulls p 416 join repos r 417 + on coalesce( 418 + nullif(p.repo_did, '') = nullif(r.repo_did, ''), 419 + p.repo_at = r.at_uri 420 + ) 421 where (p.owner_did, p.rkey) in (%s)`, 422 strings.Join(vals, ","), 423 ), ··· 449 filter := orm.FilterIn("c.comment_at", aturis) 450 rows, err := e.Query( 451 fmt.Sprintf( 452 + `select distinct r.did, r.name, p.pull_id, c.id, p.title, p.state 453 from repos r 454 join pulls p 455 + on coalesce( 456 + nullif(p.repo_did, '') = nullif(r.repo_did, ''), 457 + p.repo_at = r.at_uri 458 + ) 459 join pull_comments c 460 + on coalesce( 461 + nullif(c.repo_did, '') = nullif(r.repo_did, ''), 462 + c.repo_at = r.at_uri 463 + ) and p.pull_id = c.pull_id 464 where %s`, 465 filter.Condition(), 466 ),
+64 -15
appview/db/repos.go
··· 46 website, 47 topics, 48 source, 49 - spindle 50 from 51 repos r 52 %s ··· 64 for rows.Next() { 65 var repo models.Repo 66 var createdAt string 67 - var description, website, topicStr, source, spindle sql.NullString 68 69 err := rows.Scan( 70 &repo.Id, ··· 78 &topicStr, 79 &source, 80 &spindle, 81 ) 82 if err != nil { 83 return nil, fmt.Errorf("failed to execute repo query: %w ", err) ··· 100 } 101 if spindle.Valid { 102 repo.Spindle = spindle.String 103 } 104 105 repo.RepoStats = &models.RepoStats{} ··· 352 var nullableDescription sql.NullString 353 var nullableWebsite sql.NullString 354 var nullableTopicStr sql.NullString 355 356 - row := e.QueryRow(`select id, did, name, knot, created, rkey, description, website, topics from repos where at_uri = ?`, atUri) 357 358 var createdAt string 359 - if err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr); err != nil { 360 return nil, err 361 } 362 createdAtTime, _ := time.Parse(time.RFC3339, createdAt) ··· 370 } 371 if nullableTopicStr.Valid { 372 repo.Topics = strings.Fields(nullableTopicStr.String) 373 } 374 375 return &repo, nil 376 } 377 378 func PutRepo(tx *sql.Tx, repo models.Repo) error { 379 _, err := tx.Exec( 380 `update repos 381 - set knot = ?, description = ?, website = ?, topics = ? 382 where did = ? and rkey = ? 383 `, 384 - repo.Knot, repo.Description, repo.Website, repo.TopicStr(), repo.Did, repo.Rkey, 385 ) 386 return err 387 } 388 389 func AddRepo(tx *sql.Tx, repo *models.Repo) error { 390 _, err := tx.Exec( 391 `insert into repos 392 - (did, name, knot, rkey, at_uri, description, website, topics, source) 393 - values (?, ?, ?, ?, ?, ?, ?, ?, ?)`, 394 - repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.RepoAt().String(), repo.Description, repo.Website, repo.TopicStr(), repo.Source, 395 ) 396 if err != nil { 397 return fmt.Errorf("failed to insert repo: %w", err) ··· 401 if err := SubscribeLabel(tx, &models.RepoLabel{ 402 RepoAt: repo.RepoAt(), 403 LabelAt: syntax.ATURI(dl), 404 }); err != nil { 405 return fmt.Errorf("failed to subscribe to label: %w", err) 406 } ··· 438 var repos []models.Repo 439 440 rows, err := e.Query( 441 - `select distinct r.id, r.did, r.name, r.knot, r.rkey, r.description, r.website, r.created, r.source 442 from repos r 443 left join collaborators c on r.at_uri = c.repo_at 444 where (r.did = ? or c.subject_did = ?) ··· 458 var nullableDescription sql.NullString 459 var nullableWebsite sql.NullString 460 var nullableSource sql.NullString 461 462 - err := rows.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &createdAt, &nullableSource) 463 if err != nil { 464 return nil, err 465 } 466 467 if nullableDescription.Valid { 468 repo.Description = nullableDescription.String 469 } 470 471 if nullableSource.Valid { 472 repo.Source = nullableSource.String 473 } 474 475 createdAtTime, err := time.Parse(time.RFC3339, createdAt) ··· 496 var nullableWebsite sql.NullString 497 var nullableTopicStr sql.NullString 498 var nullableSource sql.NullString 499 500 row := e.QueryRow( 501 - `select id, did, name, knot, rkey, description, website, topics, created, source 502 from repos 503 where did = ? and name = ? and source is not null and source != ''`, 504 did, name, 505 ) 506 507 - err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr, &createdAt, &nullableSource) 508 if err != nil { 509 return nil, err 510 } ··· 523 524 if nullableSource.Valid { 525 repo.Source = nullableSource.String 526 } 527 528 createdAtTime, err := time.Parse(time.RFC3339, createdAt) ··· 535 return &repo, nil 536 } 537 538 func UpdateDescription(e Execer, repoAt, newDescription string) error { 539 _, err := e.Exec( 540 `update repos set description = ? where at_uri = ?`, newDescription, repoAt) ··· 548 } 549 550 func SubscribeLabel(e Execer, rl *models.RepoLabel) error { 551 - query := `insert or ignore into repo_labels (repo_at, label_at) values (?, ?)` 552 553 - _, err := e.Exec(query, rl.RepoAt.String(), rl.LabelAt.String()) 554 return err 555 } 556
··· 46 website, 47 topics, 48 source, 49 + spindle, 50 + repo_did 51 from 52 repos r 53 %s ··· 65 for rows.Next() { 66 var repo models.Repo 67 var createdAt string 68 + var description, website, topicStr, source, spindle, repoDid sql.NullString 69 70 err := rows.Scan( 71 &repo.Id, ··· 79 &topicStr, 80 &source, 81 &spindle, 82 + &repoDid, 83 ) 84 if err != nil { 85 return nil, fmt.Errorf("failed to execute repo query: %w ", err) ··· 102 } 103 if spindle.Valid { 104 repo.Spindle = spindle.String 105 + } 106 + if repoDid.Valid { 107 + repo.RepoDid = repoDid.String 108 } 109 110 repo.RepoStats = &models.RepoStats{} ··· 357 var nullableDescription sql.NullString 358 var nullableWebsite sql.NullString 359 var nullableTopicStr sql.NullString 360 + var nullableRepoDid sql.NullString 361 362 + row := e.QueryRow(`select id, did, name, knot, created, rkey, description, website, topics, repo_did from repos where at_uri = ?`, atUri) 363 364 var createdAt string 365 + if err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr, &nullableRepoDid); err != nil { 366 return nil, err 367 } 368 createdAtTime, _ := time.Parse(time.RFC3339, createdAt) ··· 376 } 377 if nullableTopicStr.Valid { 378 repo.Topics = strings.Fields(nullableTopicStr.String) 379 + } 380 + if nullableRepoDid.Valid { 381 + repo.RepoDid = nullableRepoDid.String 382 } 383 384 return &repo, nil 385 } 386 387 func PutRepo(tx *sql.Tx, repo models.Repo) error { 388 + var repoDid *string 389 + if repo.RepoDid != "" { 390 + repoDid = &repo.RepoDid 391 + } 392 _, err := tx.Exec( 393 `update repos 394 + set knot = ?, description = ?, website = ?, topics = ?, repo_did = coalesce(?, repo_did) 395 where did = ? and rkey = ? 396 `, 397 + repo.Knot, repo.Description, repo.Website, repo.TopicStr(), repoDid, repo.Did, repo.Rkey, 398 ) 399 return err 400 } 401 402 func AddRepo(tx *sql.Tx, repo *models.Repo) error { 403 + var repoDid *string 404 + if repo.RepoDid != "" { 405 + repoDid = &repo.RepoDid 406 + } 407 _, err := tx.Exec( 408 `insert into repos 409 + (did, name, knot, rkey, at_uri, description, website, topics, source, repo_did) 410 + values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, 411 + repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.RepoAt().String(), repo.Description, repo.Website, repo.TopicStr(), repo.Source, repoDid, 412 ) 413 if err != nil { 414 return fmt.Errorf("failed to insert repo: %w", err) ··· 418 if err := SubscribeLabel(tx, &models.RepoLabel{ 419 RepoAt: repo.RepoAt(), 420 LabelAt: syntax.ATURI(dl), 421 + RepoDid: repo.RepoDid, 422 }); err != nil { 423 return fmt.Errorf("failed to subscribe to label: %w", err) 424 } ··· 456 var repos []models.Repo 457 458 rows, err := e.Query( 459 + `select distinct r.id, r.did, r.name, r.knot, r.rkey, r.description, r.website, r.created, r.source, r.repo_did 460 from repos r 461 left join collaborators c on r.at_uri = c.repo_at 462 where (r.did = ? or c.subject_did = ?) ··· 476 var nullableDescription sql.NullString 477 var nullableWebsite sql.NullString 478 var nullableSource sql.NullString 479 + var nullableRepoDid sql.NullString 480 481 + err := rows.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &createdAt, &nullableSource, &nullableRepoDid) 482 if err != nil { 483 return nil, err 484 } 485 486 if nullableDescription.Valid { 487 repo.Description = nullableDescription.String 488 + } 489 + if nullableWebsite.Valid { 490 + repo.Website = nullableWebsite.String 491 } 492 493 if nullableSource.Valid { 494 repo.Source = nullableSource.String 495 + } 496 + if nullableRepoDid.Valid { 497 + repo.RepoDid = nullableRepoDid.String 498 } 499 500 createdAtTime, err := time.Parse(time.RFC3339, createdAt) ··· 521 var nullableWebsite sql.NullString 522 var nullableTopicStr sql.NullString 523 var nullableSource sql.NullString 524 + var nullableRepoDid sql.NullString 525 526 row := e.QueryRow( 527 + `select id, did, name, knot, rkey, description, website, topics, created, source, repo_did 528 from repos 529 where did = ? and name = ? and source is not null and source != ''`, 530 did, name, 531 ) 532 533 + err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr, &createdAt, &nullableSource, &nullableRepoDid) 534 if err != nil { 535 return nil, err 536 } ··· 549 550 if nullableSource.Valid { 551 repo.Source = nullableSource.String 552 + } 553 + if nullableRepoDid.Valid { 554 + repo.RepoDid = nullableRepoDid.String 555 } 556 557 createdAtTime, err := time.Parse(time.RFC3339, createdAt) ··· 564 return &repo, nil 565 } 566 567 + func GetRepoByDid(e Execer, repoDid string) (*models.Repo, error) { 568 + return GetRepo(e, orm.FilterEq("repo_did", repoDid)) 569 + } 570 + 571 + func EnqueuePdsRewrite(e Execer, userDid, repoDid, recordNsid, recordRkey, oldRepoAt string) error { 572 + _, err := e.Exec( 573 + `INSERT OR IGNORE INTO pds_rewrite_status 574 + (user_did, repo_did, record_nsid, record_rkey, old_repo_at, status) 575 + VALUES (?, ?, ?, ?, ?, 'pending')`, 576 + userDid, repoDid, recordNsid, recordRkey, oldRepoAt, 577 + ) 578 + return err 579 + } 580 + 581 func UpdateDescription(e Execer, repoAt, newDescription string) error { 582 _, err := e.Exec( 583 `update repos set description = ? where at_uri = ?`, newDescription, repoAt) ··· 591 } 592 593 func SubscribeLabel(e Execer, rl *models.RepoLabel) error { 594 + var repoDid *string 595 + if rl.RepoDid != "" { 596 + repoDid = &rl.RepoDid 597 + } 598 + query := `insert into repo_labels (repo_at, label_at, repo_did) 599 + values (?, ?, ?) 600 + on conflict(repo_at, label_at) do update set repo_did = coalesce(excluded.repo_did, repo_did)` 601 602 + _, err := e.Exec(query, rl.RepoAt.String(), rl.LabelAt.String(), repoDid) 603 return err 604 } 605
+18 -5
appview/db/star.go
··· 15 ) 16 17 func AddStar(e Execer, star *models.Star) error { 18 - query := `insert or ignore into stars (did, subject_at, rkey) values (?, ?, ?)` 19 _, err := e.Exec( 20 query, 21 star.Did, 22 star.RepoAt.String(), 23 star.Rkey, 24 ) 25 return err 26 } ··· 28 // Get a star record 29 func GetStar(e Execer, did string, subjectAt syntax.ATURI) (*models.Star, error) { 30 query := ` 31 - select did, subject_at, created, rkey 32 from stars 33 where did = ? and subject_at = ?` 34 row := e.QueryRow(query, did, subjectAt) 35 36 var star models.Star 37 var created string 38 - err := row.Scan(&star.Did, &star.RepoAt, &created, &star.Rkey) 39 if err != nil { 40 return nil, err 41 } 42 43 createdAtTime, err := time.Parse(time.RFC3339, created) ··· 153 } 154 155 repoQuery := fmt.Sprintf( 156 - `select did, subject_at, created, rkey 157 from stars 158 %s 159 order by created desc ··· 171 for rows.Next() { 172 var star models.Star 173 var created string 174 - err := rows.Scan(&star.Did, &star.RepoAt, &created, &star.Rkey) 175 if err != nil { 176 return nil, err 177 } 178 179 star.Created = time.Now()
··· 15 ) 16 17 func AddStar(e Execer, star *models.Star) error { 18 + query := `insert or ignore into stars (did, subject_at, rkey, subject_did) values (?, ?, ?, ?)` 19 + var subjectDid *string 20 + if star.SubjectDid != "" { 21 + subjectDid = &star.SubjectDid 22 + } 23 _, err := e.Exec( 24 query, 25 star.Did, 26 star.RepoAt.String(), 27 star.Rkey, 28 + subjectDid, 29 ) 30 return err 31 } ··· 33 // Get a star record 34 func GetStar(e Execer, did string, subjectAt syntax.ATURI) (*models.Star, error) { 35 query := ` 36 + select did, subject_at, created, rkey, subject_did 37 from stars 38 where did = ? and subject_at = ?` 39 row := e.QueryRow(query, did, subjectAt) 40 41 var star models.Star 42 var created string 43 + var nullableSubjectDid sql.NullString 44 + err := row.Scan(&star.Did, &star.RepoAt, &created, &star.Rkey, &nullableSubjectDid) 45 if err != nil { 46 return nil, err 47 + } 48 + if nullableSubjectDid.Valid { 49 + star.SubjectDid = nullableSubjectDid.String 50 } 51 52 createdAtTime, err := time.Parse(time.RFC3339, created) ··· 162 } 163 164 repoQuery := fmt.Sprintf( 165 + `select did, subject_at, created, rkey, subject_did 166 from stars 167 %s 168 order by created desc ··· 180 for rows.Next() { 181 var star models.Star 182 var created string 183 + var nullableSubjectDid sql.NullString 184 + err := rows.Scan(&star.Did, &star.RepoAt, &created, &star.Rkey, &nullableSubjectDid) 185 if err != nil { 186 return nil, err 187 + } 188 + if nullableSubjectDid.Valid { 189 + star.SubjectDid = nullableSubjectDid.String 190 } 191 192 star.Created = time.Now()
+50 -5
appview/ingester.go
··· 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "log/slog" 8 "maps" ··· 121 l.Error("invalid record", "err", err) 122 return err 123 } 124 - err = db.AddStar(i.Db, &models.Star{ 125 Did: did, 126 RepoAt: subjectUri, 127 Rkey: e.Commit.RKey, 128 - }) 129 case jmodels.CommitOperationDelete: 130 err = db.DeleteStarByRkey(i.Db, did, e.Commit.RKey) 131 } ··· 225 return err 226 } 227 228 - repo, err := db.GetRepoByAtUri(i.Db, repoAt.String()) 229 - if err != nil { 230 - return err 231 } 232 233 ok, err := i.Enforcer.E.Enforce(did, repo.Knot, repo.DidSlashRepo(), "repo:push") ··· 235 return err 236 } 237 238 createdAt, err := time.Parse(time.RFC3339, record.CreatedAt) 239 if err != nil { 240 createdAt = time.Now() ··· 244 Did: did, 245 Rkey: e.Commit.RKey, 246 RepoAt: repoAt, 247 Tag: plumbing.Hash(record.Tag), 248 CreatedAt: createdAt, 249 BlobCid: cid.Cid(record.Artifact.Ref), ··· 824 825 if err := i.Validator.ValidateIssue(&issue); err != nil { 826 return fmt.Errorf("failed to validate issue: %w", err) 827 } 828 829 tx, err := ddb.BeginTx(ctx, nil)
··· 2 3 import ( 4 "context" 5 + "database/sql" 6 "encoding/json" 7 + "errors" 8 "fmt" 9 "log/slog" 10 "maps" ··· 123 l.Error("invalid record", "err", err) 124 return err 125 } 126 + star := &models.Star{ 127 Did: did, 128 RepoAt: subjectUri, 129 Rkey: e.Commit.RKey, 130 + } 131 + if record.SubjectDid != nil { 132 + star.SubjectDid = *record.SubjectDid 133 + } 134 + if star.SubjectDid == "" { 135 + repo, repoErr := db.GetRepoByAtUri(i.Db, subjectUri.String()) 136 + if repoErr == nil && repo.RepoDid != "" { 137 + star.SubjectDid = repo.RepoDid 138 + if enqErr := db.EnqueuePdsRewrite(i.Db, did, repo.RepoDid, tangled.FeedStarNSID, e.Commit.RKey, record.Subject); enqErr != nil { 139 + l.Warn("failed to enqueue PDS rewrite for star", "err", enqErr, "did", did, "repoDid", repo.RepoDid) 140 + } 141 + } 142 + } 143 + err = db.AddStar(i.Db, star) 144 case jmodels.CommitOperationDelete: 145 err = db.DeleteStarByRkey(i.Db, did, e.Commit.RKey) 146 } ··· 240 return err 241 } 242 243 + var repo *models.Repo 244 + if record.RepoDid != nil && *record.RepoDid != "" { 245 + repo, err = db.GetRepoByDid(i.Db, *record.RepoDid) 246 + if err != nil && !errors.Is(err, sql.ErrNoRows) { 247 + return fmt.Errorf("failed to look up repo by DID %s: %w", *record.RepoDid, err) 248 + } 249 + } 250 + if repo == nil { 251 + repo, err = db.GetRepoByAtUri(i.Db, repoAt.String()) 252 + if err != nil { 253 + return err 254 + } 255 } 256 257 ok, err := i.Enforcer.E.Enforce(did, repo.Knot, repo.DidSlashRepo(), "repo:push") ··· 259 return err 260 } 261 262 + repoDid := repo.RepoDid 263 + if repoDid == "" && record.RepoDid != nil { 264 + repoDid = *record.RepoDid 265 + } 266 + if repoDid != "" && (record.RepoDid == nil || *record.RepoDid == "") { 267 + if enqErr := db.EnqueuePdsRewrite(i.Db, did, repoDid, tangled.RepoArtifactNSID, e.Commit.RKey, record.Repo); enqErr != nil { 268 + l.Warn("failed to enqueue PDS rewrite for artifact", "err", enqErr, "did", did, "repoDid", repoDid) 269 + } 270 + } 271 + 272 createdAt, err := time.Parse(time.RFC3339, record.CreatedAt) 273 if err != nil { 274 createdAt = time.Now() ··· 278 Did: did, 279 Rkey: e.Commit.RKey, 280 RepoAt: repoAt, 281 + RepoDid: repoDid, 282 Tag: plumbing.Hash(record.Tag), 283 CreatedAt: createdAt, 284 BlobCid: cid.Cid(record.Artifact.Ref), ··· 859 860 if err := i.Validator.ValidateIssue(&issue); err != nil { 861 return fmt.Errorf("failed to validate issue: %w", err) 862 + } 863 + 864 + if issue.RepoDid == "" { 865 + repo, repoErr := db.GetRepoByAtUri(i.Db, record.Repo) 866 + if repoErr == nil && repo.RepoDid != "" { 867 + issue.RepoDid = repo.RepoDid 868 + if enqErr := db.EnqueuePdsRewrite(i.Db, did, repo.RepoDid, tangled.RepoIssueNSID, rkey, record.Repo); enqErr != nil { 869 + l.Warn("failed to enqueue PDS rewrite for issue", "err", enqErr, "did", did, "repoDid", repo.RepoDid) 870 + } 871 + } 872 } 873 874 tx, err := ddb.BeginTx(ctx, nil)
+27 -3
appview/middleware/middleware.go
··· 17 "tangled.org/core/appview/pages" 18 "tangled.org/core/appview/pagination" 19 "tangled.org/core/appview/reporesolver" 20 "tangled.org/core/idresolver" 21 "tangled.org/core/orm" 22 "tangled.org/core/rbac" ··· 188 189 id, err := mw.idResolver.ResolveIdent(req.Context(), didOrHandle) 190 if err != nil { 191 - // invalid did or handle 192 log.Printf("failed to resolve did/handle '%s': %s\n", didOrHandle, err) 193 mw.pages.Error404(w) 194 return ··· 226 return 227 } 228 229 ctx := context.WithValue(req.Context(), "repo", repo) 230 next.ServeHTTP(w, req.WithContext(ctx)) 231 }) ··· 334 335 if r.Header.Get("User-Agent") == "Go-http-client/1.1" { 336 if r.URL.Query().Get("go-get") == "1" { 337 html := fmt.Sprintf( 338 `<meta name="go-import" content="tangled.sh/%s git https://tangled.sh/%s"/> 339 <meta name="go-import" content="tangled.org/%s git https://tangled.org/%s"/>`, 340 - fullName, fullName, 341 - fullName, fullName, 342 ) 343 w.Header().Set("Content-Type", "text/html") 344 w.Write([]byte(html))
··· 17 "tangled.org/core/appview/pages" 18 "tangled.org/core/appview/pagination" 19 "tangled.org/core/appview/reporesolver" 20 + "tangled.org/core/appview/state/userutil" 21 "tangled.org/core/idresolver" 22 "tangled.org/core/orm" 23 "tangled.org/core/rbac" ··· 189 190 id, err := mw.idResolver.ResolveIdent(req.Context(), didOrHandle) 191 if err != nil { 192 log.Printf("failed to resolve did/handle '%s': %s\n", didOrHandle, err) 193 mw.pages.Error404(w) 194 return ··· 226 return 227 } 228 229 + if repo.RepoDid != "" && req.Context().Value("repoDidCanonical") == nil { 230 + gitPaths := []string{"/info/refs", "/git-upload-pack", "/git-receive-pack", "/git-upload-archive"} 231 + user := chi.URLParam(req, "user") 232 + repoParam := chi.URLParam(req, "repo") 233 + remaining := strings.TrimPrefix(req.URL.Path, "/"+user+"/"+repoParam) 234 + 235 + isGitPath := slices.ContainsFunc(gitPaths, func(p string) bool { 236 + return strings.HasSuffix(remaining, p) 237 + }) 238 + 239 + if !isGitPath && req.URL.Query().Get("go-get") != "1" { 240 + target := "/" + repo.RepoDid + remaining 241 + if req.URL.RawQuery != "" { 242 + target += "?" + req.URL.RawQuery 243 + } 244 + http.Redirect(w, req, target, http.StatusFound) 245 + return 246 + } 247 + } 248 + 249 ctx := context.WithValue(req.Context(), "repo", repo) 250 next.ServeHTTP(w, req.WithContext(ctx)) 251 }) ··· 354 355 if r.Header.Get("User-Agent") == "Go-http-client/1.1" { 356 if r.URL.Query().Get("go-get") == "1" { 357 + modulePath := userutil.FlattenDid(fullName) 358 + if strings.Contains(modulePath, ":") { 359 + modulePath = userutil.FlattenDid(f.Did) + "/" + f.Name 360 + } 361 html := fmt.Sprintf( 362 `<meta name="go-import" content="tangled.sh/%s git https://tangled.sh/%s"/> 363 <meta name="go-import" content="tangled.org/%s git https://tangled.org/%s"/>`, 364 + modulePath, fullName, 365 + modulePath, fullName, 366 ) 367 w.Header().Set("Content-Type", "text/html") 368 w.Write([]byte(html))
+1
appview/models/artifact.go
··· 16 Rkey string 17 18 RepoAt syntax.ATURI 19 Tag plumbing.Hash 20 CreatedAt time.Time 21
··· 16 Rkey string 17 18 RepoAt syntax.ATURI 19 + RepoDid string 20 Tag plumbing.Hash 21 CreatedAt time.Time 22
+7
appview/models/issue.go
··· 14 Did string 15 Rkey string 16 RepoAt syntax.ATURI 17 IssueId int 18 Created time.Time 19 Edited *time.Time ··· 161 body = *record.Body 162 } 163 164 return Issue{ 165 RepoAt: syntax.ATURI(record.Repo), 166 Did: did, 167 Rkey: rkey, 168 Created: created,
··· 14 Did string 15 Rkey string 16 RepoAt syntax.ATURI 17 + RepoDid string 18 IssueId int 19 Created time.Time 20 Edited *time.Time ··· 162 body = *record.Body 163 } 164 165 + repoDid := "" 166 + if record.RepoDid != nil { 167 + repoDid = *record.RepoDid 168 + } 169 + 170 return Issue{ 171 RepoAt: syntax.ATURI(record.Repo), 172 + RepoDid: repoDid, 173 Did: did, 174 Rkey: rkey, 175 Created: created,
+11
appview/models/repo.go
··· 22 Topics []string 23 Spindle string 24 Labels []string 25 26 // optionally, populate this when querying for reverse mappings 27 RepoStats *RepoStats ··· 47 48 if r.Website != "" { 49 website = &r.Website 50 } 51 52 return tangled.Repo{ ··· 59 Source: source, 60 Spindle: spindle, 61 Labels: r.Labels, 62 } 63 } 64 ··· 67 } 68 69 func (r Repo) DidSlashRepo() string { 70 p, _ := securejoin.SecureJoin(r.Did, r.Name) 71 return p 72 } ··· 98 Id int64 99 RepoAt syntax.ATURI 100 LabelAt syntax.ATURI 101 } 102 103 type RepoGroup struct {
··· 22 Topics []string 23 Spindle string 24 Labels []string 25 + RepoDid string 26 27 // optionally, populate this when querying for reverse mappings 28 RepoStats *RepoStats ··· 48 49 if r.Website != "" { 50 website = &r.Website 51 + } 52 + 53 + var repoDid *string 54 + if r.RepoDid != "" { 55 + repoDid = &r.RepoDid 56 } 57 58 return tangled.Repo{ ··· 65 Source: source, 66 Spindle: spindle, 67 Labels: r.Labels, 68 + RepoDid: repoDid, 69 } 70 } 71 ··· 74 } 75 76 func (r Repo) DidSlashRepo() string { 77 + if r.RepoDid != "" { 78 + return r.RepoDid 79 + } 80 p, _ := securejoin.SecureJoin(r.Did, r.Name) 81 return p 82 } ··· 108 Id int64 109 RepoAt syntax.ATURI 110 LabelAt syntax.ATURI 111 + RepoDid string 112 } 113 114 type RepoGroup struct {
+5 -4
appview/models/star.go
··· 7 ) 8 9 type Star struct { 10 - Did string 11 - RepoAt syntax.ATURI 12 - Created time.Time 13 - Rkey string 14 } 15 16 // RepoStar is used for reverse mapping to repos
··· 7 ) 8 9 type Star struct { 10 + Did string 11 + RepoAt syntax.ATURI 12 + SubjectDid string 13 + Created time.Time 14 + Rkey string 15 } 16 17 // RepoStar is used for reverse mapping to repos
+3
appview/pages/funcmap.go
··· 78 return identity.Handle.String() 79 }, 80 "ownerSlashRepo": func(repo *models.Repo) string { 81 ownerId, err := p.resolver.ResolveIdent(context.Background(), repo.Did) 82 if err != nil { 83 return repo.DidSlashRepo()
··· 78 return identity.Handle.String() 79 }, 80 "ownerSlashRepo": func(repo *models.Repo) string { 81 + if repo.RepoDid != "" { 82 + return repo.RepoDid 83 + } 84 ownerId, err := p.resolver.ResolveIdent(context.Background(), repo.Did) 85 if err != nil { 86 return repo.DidSlashRepo()
+7
appview/pages/repoinfo/repoinfo.go
··· 20 } 21 22 func (r RepoInfo) FullName() string { 23 return path.Join(r.owner(), r.Name) 24 } 25 ··· 32 } 33 34 func (r RepoInfo) FullNameWithoutAt() string { 35 return path.Join(r.ownerWithoutAt(), r.Name) 36 } 37 ··· 59 Rkey string 60 OwnerDid string 61 OwnerHandle string 62 Description string 63 Website string 64 Topics []string
··· 20 } 21 22 func (r RepoInfo) FullName() string { 23 + if r.RepoDid != "" { 24 + return r.RepoDid 25 + } 26 return path.Join(r.owner(), r.Name) 27 } 28 ··· 35 } 36 37 func (r RepoInfo) FullNameWithoutAt() string { 38 + if r.RepoDid != "" { 39 + return userutil.FlattenDid(r.RepoDid) 40 + } 41 return path.Join(r.ownerWithoutAt(), r.Name) 42 } 43 ··· 65 Rkey string 66 OwnerDid string 67 OwnerHandle string 68 + RepoDid string 69 Description string 70 Website string 71 Topics []string
+2
appview/repo/repo.go
··· 312 err = db.SubscribeLabel(tx, &models.RepoLabel{ 313 RepoAt: f.RepoAt(), 314 LabelAt: label.AtUri(), 315 }) 316 317 err = tx.Commit() ··· 504 err = db.SubscribeLabel(tx, &models.RepoLabel{ 505 RepoAt: f.RepoAt(), 506 LabelAt: syntax.ATURI(l), 507 }) 508 if err != nil { 509 fail("Failed to subscribe to label.", err)
··· 312 err = db.SubscribeLabel(tx, &models.RepoLabel{ 313 RepoAt: f.RepoAt(), 314 LabelAt: label.AtUri(), 315 + RepoDid: f.RepoDid, 316 }) 317 318 err = tx.Commit() ··· 505 err = db.SubscribeLabel(tx, &models.RepoLabel{ 506 RepoAt: f.RepoAt(), 507 LabelAt: syntax.ATURI(l), 508 + RepoDid: f.RepoDid, 509 }) 510 if err != nil { 511 fail("Failed to subscribe to label.", err)
+4
appview/reporesolver/resolver.go
··· 30 31 // NOTE: this... should not even be here. the entire package will be removed in future refactor 32 func GetBaseRepoPath(r *http.Request, repo *models.Repo) string { 33 var ( 34 user = chi.URLParam(r, "user") 35 name = chi.URLParam(r, "repo") ··· 108 // this is basically a models.Repo 109 OwnerDid: ownerId.DID.String(), 110 OwnerHandle: ownerId.Handle.String(), 111 Name: repo.Name, 112 Rkey: repo.Rkey, 113 Description: repo.Description,
··· 30 31 // NOTE: this... should not even be here. the entire package will be removed in future refactor 32 func GetBaseRepoPath(r *http.Request, repo *models.Repo) string { 33 + if repo.RepoDid != "" { 34 + return repo.RepoDid 35 + } 36 var ( 37 user = chi.URLParam(r, "user") 38 name = chi.URLParam(r, "repo") ··· 111 // this is basically a models.Repo 112 OwnerDid: ownerId.DID.String(), 113 OwnerHandle: ownerId.Handle.String(), 114 + RepoDid: repo.RepoDid, 115 Name: repo.Name, 116 Rkey: repo.Rkey, 117 Description: repo.Description,
+7 -20
appview/state/git_http.go
··· 12 ) 13 14 func (s *State) InfoRefs(w http.ResponseWriter, r *http.Request) { 15 - user := r.Context().Value("resolvedId").(identity.Identity) 16 repo := r.Context().Value("repo").(*models.Repo) 17 18 scheme := "https" ··· 20 scheme = "http" 21 } 22 23 - targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 24 s.proxyRequest(w, r, targetURL) 25 26 } 27 28 func (s *State) UploadArchive(w http.ResponseWriter, r *http.Request) { 29 - user, ok := r.Context().Value("resolvedId").(identity.Identity) 30 - if !ok { 31 - http.Error(w, "failed to resolve user", http.StatusInternalServerError) 32 - return 33 - } 34 repo := r.Context().Value("repo").(*models.Repo) 35 36 scheme := "https" ··· 38 scheme = "http" 39 } 40 41 - targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-archive?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 42 s.proxyRequest(w, r, targetURL) 43 } 44 45 func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) { 46 - user, ok := r.Context().Value("resolvedId").(identity.Identity) 47 - if !ok { 48 - http.Error(w, "failed to resolve user", http.StatusInternalServerError) 49 - return 50 - } 51 repo := r.Context().Value("repo").(*models.Repo) 52 53 scheme := "https" ··· 55 scheme = "http" 56 } 57 58 - targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 59 s.proxyRequest(w, r, targetURL) 60 } 61 62 func (s *State) ReceivePack(w http.ResponseWriter, r *http.Request) { 63 - user, ok := r.Context().Value("resolvedId").(identity.Identity) 64 - if !ok { 65 - http.Error(w, "failed to resolve user", http.StatusInternalServerError) 66 - return 67 - } 68 repo := r.Context().Value("repo").(*models.Repo) 69 70 scheme := "https" ··· 72 scheme = "http" 73 } 74 75 - targetURL := fmt.Sprintf("%s://%s/%s/%s/git-receive-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 76 s.proxyRequest(w, r, targetURL) 77 } 78 ··· 90 proxyReq.Header = r.Header 91 92 repoOwnerHandle := chi.URLParam(r, "user") 93 proxyReq.Header.Add("x-tangled-repo-owner-handle", repoOwnerHandle) 94 95 // Execute request
··· 12 ) 13 14 func (s *State) InfoRefs(w http.ResponseWriter, r *http.Request) { 15 repo := r.Context().Value("repo").(*models.Repo) 16 17 scheme := "https" ··· 19 scheme = "http" 20 } 21 22 + targetURL := fmt.Sprintf("%s://%s/%s/info/refs?%s", scheme, repo.Knot, repo.DidSlashRepo(), r.URL.RawQuery) 23 s.proxyRequest(w, r, targetURL) 24 25 } 26 27 func (s *State) UploadArchive(w http.ResponseWriter, r *http.Request) { 28 repo := r.Context().Value("repo").(*models.Repo) 29 30 scheme := "https" ··· 32 scheme = "http" 33 } 34 35 + targetURL := fmt.Sprintf("%s://%s/%s/git-upload-archive?%s", scheme, repo.Knot, repo.DidSlashRepo(), r.URL.RawQuery) 36 s.proxyRequest(w, r, targetURL) 37 } 38 39 func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) { 40 repo := r.Context().Value("repo").(*models.Repo) 41 42 scheme := "https" ··· 44 scheme = "http" 45 } 46 47 + targetURL := fmt.Sprintf("%s://%s/%s/git-upload-pack?%s", scheme, repo.Knot, repo.DidSlashRepo(), r.URL.RawQuery) 48 s.proxyRequest(w, r, targetURL) 49 } 50 51 func (s *State) ReceivePack(w http.ResponseWriter, r *http.Request) { 52 repo := r.Context().Value("repo").(*models.Repo) 53 54 scheme := "https" ··· 56 scheme = "http" 57 } 58 59 + targetURL := fmt.Sprintf("%s://%s/%s/git-receive-pack?%s", scheme, repo.Knot, repo.DidSlashRepo(), r.URL.RawQuery) 60 s.proxyRequest(w, r, targetURL) 61 } 62 ··· 74 proxyReq.Header = r.Header 75 76 repoOwnerHandle := chi.URLParam(r, "user") 77 + if id, ok := r.Context().Value("resolvedId").(identity.Identity); ok && !id.Handle.IsInvalidHandle() { 78 + repoOwnerHandle = id.Handle.String() 79 + } 80 proxyReq.Header.Add("x-tangled-repo-owner-handle", repoOwnerHandle) 81 82 // Execute request
+82 -31
appview/state/knotstream.go
··· 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" ··· 86 return err 87 } 88 89 knownKnots, err := enforcer.GetKnotsForUser(record.CommitterDid) 90 if err != nil { 91 return err ··· 95 } 96 97 logger.Info("processing gitRefUpdate event", 98 - "repo_did", record.RepoDid, 99 "repo_name", record.RepoName, 100 "ref", record.Ref, 101 "old_sha", record.OldSha, 102 "new_sha", record.NewSha) 103 104 - // trigger webhook notifications first (before other ops that might fail) 105 var errWebhook error 106 - repos, err := db.GetRepos( 107 - d, 108 - 0, 109 - orm.FilterEq("did", record.RepoDid), 110 - orm.FilterEq("name", record.RepoName), 111 - ) 112 - if err != nil { 113 - errWebhook = fmt.Errorf("failed to lookup repo for webhooks: %w", err) 114 - } else if len(repos) == 1 { 115 notifier.Push(ctx, &repos[0], record.Ref, record.OldSha, record.NewSha, record.CommitterDid) 116 } 117 ··· 167 168 func updateRepoLanguages(d *db.DB, record tangled.GitRefUpdate) error { 169 if record.Meta == nil || record.Meta.LangBreakdown == nil || record.Meta.LangBreakdown.Inputs == nil { 170 - return fmt.Errorf("empty language data for repo: %s/%s", record.RepoDid, record.RepoName) 171 } 172 173 - repos, err := db.GetRepos( 174 - d, 175 - 0, 176 - orm.FilterEq("did", record.RepoDid), 177 - orm.FilterEq("name", record.RepoName), 178 - ) 179 - if err != nil { 180 - return fmt.Errorf("failed to look for repo in DB (%s/%s): %w", record.RepoDid, record.RepoName, err) 181 } 182 - if len(repos) != 1 { 183 - return fmt.Errorf("incorrect number of repos returned: %d (expected 1)", len(repos)) 184 } 185 - repo := repos[0] 186 187 ref := plumbing.ReferenceName(record.Ref) 188 if !ref.IsBranch() { ··· 236 } 237 238 // does this repo have a spindle configured? 239 - repos, err := db.GetRepos( 240 - d, 241 - 0, 242 - orm.FilterEq("did", record.TriggerMetadata.Repo.Did), 243 - orm.FilterEq("name", record.TriggerMetadata.Repo.Repo), 244 - ) 245 - if err != nil { 246 - return fmt.Errorf("failed to look for repo in DB: nsid %s, rkey %s, %w", msg.Nsid, msg.Rkey, err) 247 } 248 if len(repos) != 1 { 249 return fmt.Errorf("incorrect number of repos returned: %d (expected 1)", len(repos)) 250 }
··· 2 3 import ( 4 "context" 5 + "database/sql" 6 "encoding/json" 7 "errors" 8 "fmt" ··· 87 return err 88 } 89 90 + // backward compat: old knots emit owner DID as "repoDid" with no "ownerDid" field 91 + if record.OwnerDid == "" && record.RepoDid != nil && *record.RepoDid != "" { 92 + record.OwnerDid = *record.RepoDid 93 + record.RepoDid = nil 94 + } 95 + 96 knownKnots, err := enforcer.GetKnotsForUser(record.CommitterDid) 97 if err != nil { 98 return err ··· 102 } 103 104 logger.Info("processing gitRefUpdate event", 105 + "owner_did", record.OwnerDid, 106 "repo_name", record.RepoName, 107 + "repo_did", record.RepoDid, 108 "ref", record.Ref, 109 "old_sha", record.OldSha, 110 "new_sha", record.NewSha) 111 112 var errWebhook error 113 + var repos []models.Repo 114 + 115 + if record.RepoDid != nil && *record.RepoDid != "" { 116 + repo, lookupErr := db.GetRepoByDid(d, *record.RepoDid) 117 + switch { 118 + case lookupErr == nil: 119 + repos = []models.Repo{*repo} 120 + case !errors.Is(lookupErr, sql.ErrNoRows): 121 + logger.Warn("failed to look up repo by DID", "err", lookupErr, "repoDid", *record.RepoDid) 122 + } 123 + } 124 + 125 + if len(repos) == 0 { 126 + repos, err = db.GetRepos( 127 + d, 128 + 0, 129 + orm.FilterEq("did", record.OwnerDid), 130 + orm.FilterEq("name", record.RepoName), 131 + ) 132 + if err != nil { 133 + errWebhook = fmt.Errorf("failed to lookup repo for webhooks: %w", err) 134 + } 135 + } 136 + 137 + if errWebhook == nil && len(repos) == 1 { 138 notifier.Push(ctx, &repos[0], record.Ref, record.OldSha, record.NewSha, record.CommitterDid) 139 } 140 ··· 190 191 func updateRepoLanguages(d *db.DB, record tangled.GitRefUpdate) error { 192 if record.Meta == nil || record.Meta.LangBreakdown == nil || record.Meta.LangBreakdown.Inputs == nil { 193 + return fmt.Errorf("empty language data for repo: %s/%s", record.OwnerDid, record.RepoName) 194 } 195 196 + var repo models.Repo 197 + if record.RepoDid != nil && *record.RepoDid != "" { 198 + r, lookupErr := db.GetRepoByDid(d, *record.RepoDid) 199 + switch { 200 + case lookupErr == nil: 201 + repo = *r 202 + case !errors.Is(lookupErr, sql.ErrNoRows): 203 + return fmt.Errorf("failed to look up repo by DID %s: %w", *record.RepoDid, lookupErr) 204 + } 205 } 206 + 207 + if repo.Id == 0 { 208 + repos, err := db.GetRepos( 209 + d, 210 + 0, 211 + orm.FilterEq("did", record.OwnerDid), 212 + orm.FilterEq("name", record.RepoName), 213 + ) 214 + if err != nil { 215 + return fmt.Errorf("failed to look for repo in DB (%s/%s): %w", record.OwnerDid, record.RepoName, err) 216 + } 217 + if len(repos) != 1 { 218 + return fmt.Errorf("incorrect number of repos returned: %d (expected 1)", len(repos)) 219 + } 220 + repo = repos[0] 221 } 222 223 ref := plumbing.ReferenceName(record.Ref) 224 if !ref.IsBranch() { ··· 272 } 273 274 // does this repo have a spindle configured? 275 + var repos []models.Repo 276 + if record.TriggerMetadata.Repo.RepoDid != nil && *record.TriggerMetadata.Repo.RepoDid != "" { 277 + repo, lookupErr := db.GetRepoByDid(d, *record.TriggerMetadata.Repo.RepoDid) 278 + switch { 279 + case lookupErr == nil: 280 + repos = []models.Repo{*repo} 281 + case !errors.Is(lookupErr, sql.ErrNoRows): 282 + return fmt.Errorf("failed to look up repo by DID %s: %w", *record.TriggerMetadata.Repo.RepoDid, lookupErr) 283 + } 284 + } 285 + 286 + if len(repos) == 0 { 287 + var err error 288 + repos, err = db.GetRepos( 289 + d, 290 + 0, 291 + orm.FilterEq("did", record.TriggerMetadata.Repo.Did), 292 + orm.FilterEq("name", record.TriggerMetadata.Repo.Repo), 293 + ) 294 + if err != nil { 295 + return fmt.Errorf("failed to look for repo in DB: nsid %s, rkey %s, %w", msg.Nsid, msg.Rkey, err) 296 + } 297 } 298 + 299 if len(repos) != 1 { 300 return fmt.Errorf("incorrect number of repos returned: %d (expected 1)", len(repos)) 301 }
+28 -2
appview/state/router.go
··· 1 package state 2 3 import ( 4 "net/http" 5 "strings" 6 7 "github.com/go-chi/chi/v5" 8 "tangled.org/core/appview/issues" 9 "tangled.org/core/appview/knots" 10 "tangled.org/core/appview/labels" ··· 45 if len(pathParts) > 0 { 46 firstPart := pathParts[0] 47 48 - // if using a DID or handle, just continue as per usual 49 - if userutil.IsDid(firstPart) || userutil.IsHandle(firstPart) { 50 userRouter.ServeHTTP(w, r) 51 return 52 }
··· 1 package state 2 3 import ( 4 + "context" 5 + "database/sql" 6 + "errors" 7 "net/http" 8 "strings" 9 10 "github.com/go-chi/chi/v5" 11 + "tangled.org/core/appview/db" 12 "tangled.org/core/appview/issues" 13 "tangled.org/core/appview/knots" 14 "tangled.org/core/appview/labels" ··· 49 if len(pathParts) > 0 { 50 firstPart := pathParts[0] 51 52 + if userutil.IsDid(firstPart) { 53 + repo, err := db.GetRepoByDid(s.db, firstPart) 54 + switch { 55 + case err == nil: 56 + remaining := "" 57 + if len(pathParts) > 1 { 58 + remaining = "/" + pathParts[1] 59 + } 60 + rewritten := "/" + repo.Did + "/" + repo.Name + remaining 61 + r2 := r.Clone(r.Context()) 62 + r2.URL.Path = rewritten 63 + r2.URL.RawPath = rewritten 64 + ctx := context.WithValue(r2.Context(), "repoDidCanonical", true) 65 + userRouter.ServeHTTP(w, r2.WithContext(ctx)) 66 + case errors.Is(err, sql.ErrNoRows): 67 + userRouter.ServeHTTP(w, r) 68 + default: 69 + s.logger.Error("db error looking up repo DID", "repoDid", firstPart, "err", err) 70 + http.Error(w, "internal server error", http.StatusInternalServerError) 71 + } 72 + return 73 + } 74 + 75 + if userutil.IsHandle(firstPart) { 76 userRouter.ServeHTTP(w, r) 77 return 78 }
+108
knotserver/db/db.go
··· 3 import ( 4 "context" 5 "database/sql" 6 "log/slog" 7 "strings" 8 9 _ "github.com/mattn/go-sqlite3" 10 "tangled.org/core/log" 11 ) ··· 86 return nil, err 87 } 88 89 return &DB{ 90 db: db, 91 logger: logger, 92 }, nil 93 }
··· 3 import ( 4 "context" 5 "database/sql" 6 + "fmt" 7 "log/slog" 8 + "os" 9 "strings" 10 11 + securejoin "github.com/cyphar/filepath-securejoin" 12 _ "github.com/mattn/go-sqlite3" 13 "tangled.org/core/log" 14 ) ··· 89 return nil, err 90 } 91 92 + migrationCheck := func(name string) bool { 93 + var count int 94 + conn.QueryRowContext(ctx, `SELECT count(1) FROM migrations WHERE name = ?`, name).Scan(&count) 95 + return count > 0 96 + } 97 + 98 + runMigration := func(name string, fn func() error) { 99 + if migrationCheck(name) { 100 + return 101 + } 102 + if err := fn(); err != nil { 103 + logger.Warn("migration failed", "name", name, "err", err) 104 + return 105 + } 106 + conn.ExecContext(ctx, `INSERT INTO migrations (name) VALUES (?)`, name) 107 + } 108 + 109 + runMigration("add-owner-did-to-repo-keys", func() error { 110 + _, mErr := conn.ExecContext(ctx, `ALTER TABLE repo_keys ADD COLUMN owner_did TEXT`) 111 + return mErr 112 + }) 113 + 114 + runMigration("add-repo-name-to-repo-keys", func() error { 115 + _, mErr := conn.ExecContext(ctx, `ALTER TABLE repo_keys ADD COLUMN repo_name TEXT`) 116 + return mErr 117 + }) 118 + 119 + runMigration("add-unique-owner-repo-on-repo-keys", func() error { 120 + _, mErr := conn.ExecContext(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_repo_keys_owner_repo ON repo_keys(owner_did, repo_name)`) 121 + return mErr 122 + }) 123 + 124 return &DB{ 125 db: db, 126 logger: logger, 127 }, nil 128 } 129 + 130 + func (d *DB) ResolveAtUri(atUri string) (string, error) { 131 + var repoDid string 132 + err := d.db.QueryRow( 133 + `SELECT repo_did FROM repo_at_history WHERE old_repo_at = ?`, 134 + atUri, 135 + ).Scan(&repoDid) 136 + return repoDid, err 137 + } 138 + 139 + func (d *DB) GetRepoDid(ownerDid, repoName string) (string, error) { 140 + var repoDid string 141 + err := d.db.QueryRow( 142 + `SELECT repo_did FROM repo_keys WHERE owner_did = ? AND repo_name = ?`, 143 + ownerDid, repoName, 144 + ).Scan(&repoDid) 145 + return repoDid, err 146 + } 147 + 148 + func (d *DB) GetRepoKeyOwner(repoDid string) (ownerDid string, repoName string, err error) { 149 + var nullOwner, nullName sql.NullString 150 + err = d.db.QueryRow( 151 + `SELECT owner_did, repo_name FROM repo_keys WHERE repo_did = ?`, 152 + repoDid, 153 + ).Scan(&nullOwner, &nullName) 154 + if err != nil { 155 + return 156 + } 157 + if !nullOwner.Valid || !nullName.Valid || nullOwner.String == "" || nullName.String == "" { 158 + err = fmt.Errorf("repo_keys row for %s has empty or null owner_did or repo_name", repoDid) 159 + return 160 + } 161 + ownerDid = nullOwner.String 162 + repoName = nullName.String 163 + return 164 + } 165 + 166 + func (d *DB) ResolveRepoOnDisk(scanPath, ownerDid, repoName string) (repoPath string, repoDid string, err error) { 167 + did, lookupErr := d.GetRepoDid(ownerDid, repoName) 168 + if lookupErr == nil && did != "" { 169 + didPath, joinErr := securejoin.SecureJoin(scanPath, did) 170 + if joinErr == nil { 171 + if _, statErr := os.Stat(didPath); statErr == nil { 172 + return didPath, did, nil 173 + } 174 + } 175 + } 176 + relative, _ := securejoin.SecureJoin(ownerDid, repoName) 177 + fallback, _ := securejoin.SecureJoin(scanPath, relative) 178 + if _, statErr := os.Stat(fallback); statErr != nil { 179 + return "", "", fmt.Errorf("repo not found on disk: %s/%s", ownerDid, repoName) 180 + } 181 + return fallback, "", nil 182 + } 183 + 184 + func (d *DB) ResolveRepoDIDOnDisk(scanPath, repoDid string) (repoPath, ownerDid, repoName string, err error) { 185 + ownerDid, repoName, err = d.GetRepoKeyOwner(repoDid) 186 + if err != nil { 187 + return 188 + } 189 + 190 + didPath, joinErr := securejoin.SecureJoin(scanPath, repoDid) 191 + if joinErr == nil { 192 + if _, statErr := os.Stat(didPath); statErr == nil { 193 + repoPath = didPath 194 + return 195 + } 196 + } 197 + 198 + relative, _ := securejoin.SecureJoin(ownerDid, repoName) 199 + repoPath, _ = securejoin.SecureJoin(scanPath, relative) 200 + return 201 + }
+26 -24
knotserver/git.go
··· 5 "fmt" 6 "io" 7 "net/http" 8 - "path/filepath" 9 "strings" 10 11 - securejoin "github.com/cyphar/filepath-securejoin" 12 "github.com/go-chi/chi/v5" 13 "tangled.org/core/knotserver/git/service" 14 ) 15 16 - func (h *Knot) InfoRefs(w http.ResponseWriter, r *http.Request) { 17 did := chi.URLParam(r, "did") 18 name := chi.URLParam(r, "name") 19 - repoName, err := securejoin.SecureJoin(did, name) 20 if err != nil { 21 - gitError(w, "repository not found", http.StatusNotFound) 22 - h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err) 23 - return 24 } 25 26 - repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, repoName) 27 if err != nil { 28 gitError(w, "repository not found", http.StatusNotFound) 29 - h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err) 30 return 31 } 32 ··· 57 } 58 59 func (h *Knot) UploadArchive(w http.ResponseWriter, r *http.Request) { 60 - did := chi.URLParam(r, "did") 61 - name := chi.URLParam(r, "name") 62 - repo, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 63 if err != nil { 64 - gitError(w, err.Error(), http.StatusInternalServerError) 65 - h.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err) 66 return 67 } 68 ··· 104 } 105 106 func (h *Knot) UploadPack(w http.ResponseWriter, r *http.Request) { 107 - did := chi.URLParam(r, "did") 108 - name := chi.URLParam(r, "name") 109 - repo, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 110 if err != nil { 111 - gitError(w, err.Error(), http.StatusInternalServerError) 112 - h.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err) 113 return 114 } 115 ··· 153 } 154 155 func (h *Knot) ReceivePack(w http.ResponseWriter, r *http.Request) { 156 - did := chi.URLParam(r, "did") 157 - name := chi.URLParam(r, "name") 158 - _, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 159 if err != nil { 160 - gitError(w, err.Error(), http.StatusForbidden) 161 - h.l.Error("git: failed to secure join repo path", "handler", "ReceivePack", "error", err) 162 return 163 } 164
··· 5 "fmt" 6 "io" 7 "net/http" 8 "strings" 9 10 "github.com/go-chi/chi/v5" 11 "tangled.org/core/knotserver/git/service" 12 ) 13 14 + func (h *Knot) resolveRepoPath(r *http.Request) (string, string, error) { 15 did := chi.URLParam(r, "did") 16 name := chi.URLParam(r, "name") 17 + 18 + if name == "" && strings.HasPrefix(did, "did:") { 19 + repoPath, _, repoName, err := h.db.ResolveRepoDIDOnDisk(h.c.Repo.ScanPath, did) 20 + if err != nil { 21 + return "", "", fmt.Errorf("unknown repo DID: %w", err) 22 + } 23 + return repoPath, repoName, nil 24 + } 25 + 26 + repoPath, _, err := h.db.ResolveRepoOnDisk(h.c.Repo.ScanPath, did, name) 27 if err != nil { 28 + return "", "", fmt.Errorf("repo not found: %w", err) 29 } 30 + return repoPath, name, nil 31 + } 32 33 + func (h *Knot) InfoRefs(w http.ResponseWriter, r *http.Request) { 34 + repoPath, name, err := h.resolveRepoPath(r) 35 if err != nil { 36 gitError(w, "repository not found", http.StatusNotFound) 37 + h.l.Error("git: failed to resolve repo path", "handler", "InfoRefs", "error", err) 38 return 39 } 40 ··· 65 } 66 67 func (h *Knot) UploadArchive(w http.ResponseWriter, r *http.Request) { 68 + repo, _, err := h.resolveRepoPath(r) 69 if err != nil { 70 + gitError(w, "repository not found", http.StatusNotFound) 71 + h.l.Error("git: failed to resolve repo path", "handler", "UploadArchive", "error", err) 72 return 73 } 74 ··· 110 } 111 112 func (h *Knot) UploadPack(w http.ResponseWriter, r *http.Request) { 113 + repo, _, err := h.resolveRepoPath(r) 114 if err != nil { 115 + gitError(w, "repository not found", http.StatusNotFound) 116 + h.l.Error("git: failed to resolve repo path", "handler", "UploadPack", "error", err) 117 return 118 } 119 ··· 157 } 158 159 func (h *Knot) ReceivePack(w http.ResponseWriter, r *http.Request) { 160 + _, name, err := h.resolveRepoPath(r) 161 if err != nil { 162 + gitError(w, "repository not found", http.StatusNotFound) 163 + h.l.Error("git: failed to resolve repo path", "handler", "ReceivePack", "error", err) 164 return 165 } 166
+96 -67
knotserver/internal.go
··· 3 import ( 4 "context" 5 "encoding/json" 6 - "errors" 7 "fmt" 8 "log/slog" 9 "net/http" ··· 87 return 88 } 89 90 - // did:foo/repo-name or 91 - // handle/repo-name or 92 - // any of the above with a leading slash (/) 93 components := strings.Split(strings.TrimPrefix(strings.Trim(repo, "'"), "/"), "/") 94 l.Info("command components", "components", components) 95 96 - if len(components) != 2 { 97 - w.WriteHeader(http.StatusBadRequest) 98 - l.Error("invalid repo format", "components", components) 99 - fmt.Fprintln(w, "invalid repo format, needs <user>/<repo> or /<user>/<repo>") 100 - return 101 - } 102 - repoOwner := components[0] 103 - repoName := components[1] 104 105 - resolver := idresolver.DefaultResolver(h.c.Server.PlcUrl) 106 107 - repoOwnerIdent, err := resolver.ResolveIdent(r.Context(), repoOwner) 108 - if err != nil || repoOwnerIdent.Handle.IsInvalidHandle() { 109 - l.Error("Error resolving handle", "handle", repoOwner, "err", err) 110 - w.WriteHeader(http.StatusInternalServerError) 111 - fmt.Fprintf(w, "error resolving handle: invalid handle\n") 112 return 113 } 114 - repoOwnerDid := repoOwnerIdent.DID.String() 115 - 116 - qualifiedRepo, _ := securejoin.SecureJoin(repoOwnerDid, repoName) 117 118 if gitCommand == "git-receive-pack" { 119 - ok, err := h.e.IsPushAllowed(incomingUser, rbac.ThisServer, qualifiedRepo) 120 if err != nil || !ok { 121 w.WriteHeader(http.StatusForbidden) 122 fmt.Fprint(w, repo) ··· 125 } 126 127 w.WriteHeader(http.StatusOK) 128 - fmt.Fprint(w, qualifiedRepo) 129 } 130 131 type PushOptions struct { ··· 140 gitRelativeDir, err := filepath.Rel(h.c.Repo.ScanPath, gitAbsoluteDir) 141 if err != nil { 142 l.Error("failed to calculate relative git dir", "scanPath", h.c.Repo.ScanPath, "gitAbsoluteDir", gitAbsoluteDir) 143 return 144 } 145 146 parts := strings.SplitN(gitRelativeDir, "/", 2) 147 - if len(parts) != 2 { 148 l.Error("invalid git dir", "gitRelativeDir", gitRelativeDir) 149 return 150 } 151 - repoDid := parts[0] 152 - repoName := parts[1] 153 154 gitUserDid := r.Header.Get("X-Git-User-Did") 155 ··· 176 } 177 178 for _, line := range lines { 179 - err := h.insertRefUpdate(line, gitUserDid, repoDid, repoName) 180 if err != nil { 181 l.Error("failed to insert op", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 182 // non-fatal 183 } 184 185 - err = h.emitCompareLink(&resp.Messages, line, repoDid, repoName) 186 if err != nil { 187 l.Error("failed to reply with compare link", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 188 // non-fatal 189 } 190 191 - err = h.triggerPipeline(&resp.Messages, line, gitUserDid, repoDid, repoName, pushOptions) 192 if err != nil { 193 l.Error("failed to trigger pipeline", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 194 // non-fatal ··· 198 writeJSON(w, resp) 199 } 200 201 - func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, repoDid, repoName string) error { 202 - didSlashRepo, err := securejoin.SecureJoin(repoDid, repoName) 203 - if err != nil { 204 - return err 205 - } 206 - 207 - repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, didSlashRepo) 208 - if err != nil { 209 - return err 210 } 211 212 gr, err := git.Open(repoPath, line.Ref) ··· 214 return fmt.Errorf("failed to open git repo at ref %s: %w", line.Ref, err) 215 } 216 217 - var errs error 218 meta, err := gr.RefUpdateMeta(line) 219 - errors.Join(errs, err) 220 221 metaRecord := meta.AsRecord() 222 ··· 225 NewSha: line.NewSha.String(), 226 Ref: line.Ref, 227 CommitterDid: gitUserDid, 228 - RepoDid: repoDid, 229 RepoName: repoName, 230 Meta: &metaRecord, 231 } 232 eventJson, err := json.Marshal(refUpdate) 233 if err != nil { 234 return err ··· 240 EventJson: string(eventJson), 241 } 242 243 - return errors.Join(errs, h.db.InsertEvent(event, h.n)) 244 } 245 246 func (h *InternalHandle) triggerPipeline( 247 clientMsgs *[]string, 248 line git.PostReceiveLine, 249 gitUserDid string, 250 - repoDid string, 251 repoName string, 252 pushOptions PushOptions, 253 ) error { ··· 255 return nil 256 } 257 258 - didSlashRepo, err := securejoin.SecureJoin(repoDid, repoName) 259 - if err != nil { 260 - return err 261 - } 262 - 263 - repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, didSlashRepo) 264 - if err != nil { 265 - return err 266 } 267 268 gr, err := git.Open(repoPath, line.Ref) ··· 299 NewSha: line.NewSha.String(), 300 } 301 302 compiler := workflow.Compiler{ 303 Trigger: tangled.Pipeline_TriggerMetadata{ 304 Kind: string(workflow.TriggerKindPush), 305 Push: &trigger, 306 - Repo: &tangled.Pipeline_TriggerRepo{ 307 - Did: repoDid, 308 - Knot: h.c.Server.Hostname, 309 - Repo: repoName, 310 - }, 311 }, 312 } 313 ··· 348 func (h *InternalHandle) emitCompareLink( 349 clientMsgs *[]string, 350 line git.PostReceiveLine, 351 - repoDid string, 352 repoName string, 353 ) error { 354 // this is a second push to a branch, don't reply with the link again ··· 365 366 pushedRef := plumbing.ReferenceName(line.Ref) 367 368 - userIdent, err := h.res.ResolveIdent(context.Background(), repoDid) 369 - user := repoDid 370 if err == nil { 371 user = userIdent.Handle.String() 372 } 373 374 - didSlashRepo, err := securejoin.SecureJoin(repoDid, repoName) 375 - if err != nil { 376 - return err 377 - } 378 - 379 - repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, didSlashRepo) 380 - if err != nil { 381 - return err 382 } 383 384 gr, err := git.PlainOpen(repoPath)
··· 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "log/slog" 8 "net/http" ··· 86 return 87 } 88 89 components := strings.Split(strings.TrimPrefix(strings.Trim(repo, "'"), "/"), "/") 90 l.Info("command components", "components", components) 91 92 + var rbacResource string 93 + var diskRelative string 94 95 + switch { 96 + case len(components) == 1 && strings.HasPrefix(components[0], "did:"): 97 + repoDid := components[0] 98 + repoPath, ownerDid, repoName, lookupErr := h.db.ResolveRepoDIDOnDisk(h.c.Repo.ScanPath, repoDid) 99 + if lookupErr != nil { 100 + w.WriteHeader(http.StatusNotFound) 101 + l.Error("repo DID not found", "repoDid", repoDid, "err", lookupErr) 102 + fmt.Fprintln(w, "repo not found") 103 + return 104 + } 105 + rbacResource, _ = securejoin.SecureJoin(ownerDid, repoName) 106 + rel, relErr := filepath.Rel(h.c.Repo.ScanPath, repoPath) 107 + if relErr != nil { 108 + w.WriteHeader(http.StatusInternalServerError) 109 + l.Error("failed to compute relative path", "repoPath", repoPath, "err", relErr) 110 + fmt.Fprintln(w, "internal error") 111 + return 112 + } 113 + diskRelative = rel 114 115 + case len(components) == 2: 116 + repoOwner := components[0] 117 + resolver := idresolver.DefaultResolver(h.c.Server.PlcUrl) 118 + repoOwnerIdent, resolveErr := resolver.ResolveIdent(r.Context(), repoOwner) 119 + if resolveErr != nil || repoOwnerIdent.Handle.IsInvalidHandle() { 120 + l.Error("Error resolving handle", "handle", repoOwner, "err", resolveErr) 121 + w.WriteHeader(http.StatusInternalServerError) 122 + fmt.Fprintf(w, "error resolving handle: invalid handle\n") 123 + return 124 + } 125 + qualified, _ := securejoin.SecureJoin(repoOwnerIdent.DID.String(), components[1]) 126 + rbacResource = qualified 127 + diskRelative = qualified 128 + 129 + default: 130 + w.WriteHeader(http.StatusBadRequest) 131 + l.Error("invalid repo format", "components", components) 132 + fmt.Fprintln(w, "invalid repo format, needs <user>/<repo>, /<user>/<repo>, or <repo-did>") 133 return 134 } 135 136 if gitCommand == "git-receive-pack" { 137 + ok, err := h.e.IsPushAllowed(incomingUser, rbac.ThisServer, rbacResource) 138 if err != nil || !ok { 139 w.WriteHeader(http.StatusForbidden) 140 fmt.Fprint(w, repo) ··· 143 } 144 145 w.WriteHeader(http.StatusOK) 146 + fmt.Fprint(w, diskRelative) 147 } 148 149 type PushOptions struct { ··· 158 gitRelativeDir, err := filepath.Rel(h.c.Repo.ScanPath, gitAbsoluteDir) 159 if err != nil { 160 l.Error("failed to calculate relative git dir", "scanPath", h.c.Repo.ScanPath, "gitAbsoluteDir", gitAbsoluteDir) 161 + w.WriteHeader(http.StatusInternalServerError) 162 return 163 } 164 165 parts := strings.SplitN(gitRelativeDir, "/", 2) 166 + 167 + var ownerDid, repoName string 168 + switch { 169 + case len(parts) == 2: 170 + ownerDid = parts[0] 171 + repoName = parts[1] 172 + case len(parts) == 1 && strings.HasPrefix(parts[0], "did:"): 173 + var err error 174 + ownerDid, repoName, err = h.db.GetRepoKeyOwner(parts[0]) 175 + if err != nil { 176 + l.Error("failed to resolve repo DID from git dir", "repoDid", parts[0], "err", err) 177 + w.WriteHeader(http.StatusBadRequest) 178 + return 179 + } 180 + default: 181 l.Error("invalid git dir", "gitRelativeDir", gitRelativeDir) 182 + w.WriteHeader(http.StatusBadRequest) 183 return 184 } 185 186 gitUserDid := r.Header.Get("X-Git-User-Did") 187 ··· 208 } 209 210 for _, line := range lines { 211 + err := h.insertRefUpdate(line, gitUserDid, ownerDid, repoName) 212 if err != nil { 213 l.Error("failed to insert op", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 214 // non-fatal 215 } 216 217 + err = h.emitCompareLink(&resp.Messages, line, ownerDid, repoName) 218 if err != nil { 219 l.Error("failed to reply with compare link", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 220 // non-fatal 221 } 222 223 + err = h.triggerPipeline(&resp.Messages, line, gitUserDid, ownerDid, repoName, pushOptions) 224 if err != nil { 225 l.Error("failed to trigger pipeline", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 226 // non-fatal ··· 230 writeJSON(w, resp) 231 } 232 233 + func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, ownerDid, repoName string) error { 234 + repoPath, _, resolveErr := h.db.ResolveRepoOnDisk(h.c.Repo.ScanPath, ownerDid, repoName) 235 + if resolveErr != nil { 236 + return fmt.Errorf("failed to resolve repo on disk: %w", resolveErr) 237 } 238 239 gr, err := git.Open(repoPath, line.Ref) ··· 241 return fmt.Errorf("failed to open git repo at ref %s: %w", line.Ref, err) 242 } 243 244 meta, err := gr.RefUpdateMeta(line) 245 + if err != nil { 246 + return fmt.Errorf("failed to get ref update metadata: %w", err) 247 + } 248 249 metaRecord := meta.AsRecord() 250 ··· 253 NewSha: line.NewSha.String(), 254 Ref: line.Ref, 255 CommitterDid: gitUserDid, 256 + OwnerDid: ownerDid, 257 RepoName: repoName, 258 Meta: &metaRecord, 259 } 260 + 261 + if repoDidVal, lookupErr := h.db.GetRepoDid(ownerDid, repoName); lookupErr == nil { 262 + refUpdate.RepoDid = &repoDidVal 263 + } 264 + 265 eventJson, err := json.Marshal(refUpdate) 266 if err != nil { 267 return err ··· 273 EventJson: string(eventJson), 274 } 275 276 + return h.db.InsertEvent(event, h.n) 277 } 278 279 func (h *InternalHandle) triggerPipeline( 280 clientMsgs *[]string, 281 line git.PostReceiveLine, 282 gitUserDid string, 283 + ownerDid string, 284 repoName string, 285 pushOptions PushOptions, 286 ) error { ··· 288 return nil 289 } 290 291 + repoPath, _, resolveErr := h.db.ResolveRepoOnDisk(h.c.Repo.ScanPath, ownerDid, repoName) 292 + if resolveErr != nil { 293 + return fmt.Errorf("failed to resolve repo on disk: %w", resolveErr) 294 } 295 296 gr, err := git.Open(repoPath, line.Ref) ··· 327 NewSha: line.NewSha.String(), 328 } 329 330 + triggerRepo := &tangled.Pipeline_TriggerRepo{ 331 + Did: ownerDid, 332 + Knot: h.c.Server.Hostname, 333 + Repo: repoName, 334 + } 335 + 336 + if repoDidVal, lookupErr := h.db.GetRepoDid(ownerDid, repoName); lookupErr == nil { 337 + triggerRepo.RepoDid = &repoDidVal 338 + } 339 + 340 compiler := workflow.Compiler{ 341 Trigger: tangled.Pipeline_TriggerMetadata{ 342 Kind: string(workflow.TriggerKindPush), 343 Push: &trigger, 344 + Repo: triggerRepo, 345 }, 346 } 347 ··· 382 func (h *InternalHandle) emitCompareLink( 383 clientMsgs *[]string, 384 line git.PostReceiveLine, 385 + ownerDid string, 386 repoName string, 387 ) error { 388 // this is a second push to a branch, don't reply with the link again ··· 399 400 pushedRef := plumbing.ReferenceName(line.Ref) 401 402 + userIdent, err := h.res.ResolveIdent(context.Background(), ownerDid) 403 + user := ownerDid 404 if err == nil { 405 user = userIdent.Handle.String() 406 } 407 408 + repoPath, _, resolveErr := h.db.ResolveRepoOnDisk(h.c.Repo.ScanPath, ownerDid, repoName) 409 + if resolveErr != nil { 410 + return fmt.Errorf("failed to resolve repo on disk: %w", resolveErr) 411 } 412 413 gr, err := git.PlainOpen(repoPath)
+10 -2
knotserver/router.go
··· 81 82 r.Route("/{did}", func(r chi.Router) { 83 r.Use(h.resolveDidRedirect) 84 r.Route("/{name}", func(r chi.Router) { 85 - // routes for git operations 86 r.Get("/info/refs", h.InfoRefs) 87 r.Post("/git-upload-archive", h.UploadArchive) 88 r.Post("/git-upload-pack", h.UploadPack) ··· 136 } 137 138 suffix := strings.TrimPrefix(r.URL.Path, "/"+didOrHandle) 139 - newPath := fmt.Sprintf("/%s/%s?%s", id.DID.String(), suffix, r.URL.RawQuery) 140 http.Redirect(w, r, newPath, http.StatusTemporaryRedirect) 141 }) 142 }
··· 81 82 r.Route("/{did}", func(r chi.Router) { 83 r.Use(h.resolveDidRedirect) 84 + 85 + r.Get("/info/refs", h.InfoRefs) 86 + r.Post("/git-upload-archive", h.UploadArchive) 87 + r.Post("/git-upload-pack", h.UploadPack) 88 + r.Post("/git-receive-pack", h.ReceivePack) 89 + 90 r.Route("/{name}", func(r chi.Router) { 91 r.Get("/info/refs", h.InfoRefs) 92 r.Post("/git-upload-archive", h.UploadArchive) 93 r.Post("/git-upload-pack", h.UploadPack) ··· 141 } 142 143 suffix := strings.TrimPrefix(r.URL.Path, "/"+didOrHandle) 144 + newPath := "/" + id.DID.String() + suffix 145 + if r.URL.RawQuery != "" { 146 + newPath += "?" + r.URL.RawQuery 147 + } 148 http.Redirect(w, r, newPath, http.StatusTemporaryRedirect) 149 }) 150 }
+40
knotserver/xrpc/resolve_at_uri.go
···
··· 1 + package xrpc 2 + 3 + import ( 4 + "database/sql" 5 + "errors" 6 + "net/http" 7 + "strings" 8 + 9 + "tangled.org/core/api/tangled" 10 + xrpcerr "tangled.org/core/xrpc/errors" 11 + ) 12 + 13 + func (x *Xrpc) ResolveAtUri(w http.ResponseWriter, r *http.Request) { 14 + atUri := r.URL.Query().Get("atUri") 15 + if atUri == "" || !strings.HasPrefix(atUri, "at://") { 16 + writeError(w, xrpcerr.NewXrpcError( 17 + xrpcerr.WithTag("InvalidRequest"), 18 + xrpcerr.WithMessage("missing or invalid atUri parameter"), 19 + ), http.StatusBadRequest) 20 + return 21 + } 22 + 23 + repoDid, err := x.Db.ResolveAtUri(atUri) 24 + if errors.Is(err, sql.ErrNoRows) { 25 + writeError(w, xrpcerr.NewXrpcError( 26 + xrpcerr.WithTag("NotFound"), 27 + xrpcerr.WithMessage("no repo DID found for the given at-uri"), 28 + ), http.StatusNotFound) 29 + return 30 + } 31 + if err != nil { 32 + writeError(w, xrpcerr.NewXrpcError( 33 + xrpcerr.WithTag("InternalError"), 34 + xrpcerr.WithMessage("failed to resolve at-uri"), 35 + ), http.StatusInternalServerError) 36 + return 37 + } 38 + 39 + writeJson(w, tangled.RepoResolveAtUri_Output{Did: repoDid}) 40 + }
+14 -16
knotserver/xrpc/xrpc.go
··· 6 "net/http" 7 "strings" 8 9 - securejoin "github.com/cyphar/filepath-securejoin" 10 "tangled.org/core/api/tangled" 11 "tangled.org/core/idresolver" 12 "tangled.org/core/jetstream" ··· 68 r.Get("/"+tangled.RepoArchiveNSID, x.RepoArchive) 69 r.Get("/"+tangled.RepoLanguagesNSID, x.RepoLanguages) 70 71 // knot query endpoints (no auth required) 72 r.Get("/"+tangled.KnotListKeysNSID, x.ListKeys) 73 r.Get("/"+tangled.KnotVersionNSID, x.Version) ··· 78 return r 79 } 80 81 - // parseRepoParam parses a repo parameter in 'did/repoName' format and returns 82 - // the full repository path on disk 83 func (x *Xrpc) parseRepoParam(repo string) (string, error) { 84 if repo == "" { 85 return "", xrpcerr.NewXrpcError( ··· 88 ) 89 } 90 91 - // Parse repo string (did/repoName format) 92 parts := strings.SplitN(repo, "/", 2) 93 - if len(parts) != 2 { 94 return "", xrpcerr.NewXrpcError( 95 xrpcerr.WithTag("InvalidRequest"), 96 xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"), 97 ) 98 } 99 100 - did := parts[0] 101 - repoName := parts[1] 102 - 103 - // Construct repository path using the same logic as didPath 104 - didRepoPath, err := securejoin.SecureJoin(did, repoName) 105 if err != nil { 106 return "", xrpcerr.RepoNotFoundError 107 } 108 - 109 - repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, didRepoPath) 110 - if err != nil { 111 - return "", xrpcerr.RepoNotFoundError 112 - } 113 - 114 return repoPath, nil 115 } 116
··· 6 "net/http" 7 "strings" 8 9 "tangled.org/core/api/tangled" 10 "tangled.org/core/idresolver" 11 "tangled.org/core/jetstream" ··· 67 r.Get("/"+tangled.RepoArchiveNSID, x.RepoArchive) 68 r.Get("/"+tangled.RepoLanguagesNSID, x.RepoLanguages) 69 70 + r.Get("/"+tangled.RepoResolveAtUriNSID, x.ResolveAtUri) 71 + 72 // knot query endpoints (no auth required) 73 r.Get("/"+tangled.KnotListKeysNSID, x.ListKeys) 74 r.Get("/"+tangled.KnotVersionNSID, x.Version) ··· 79 return r 80 } 81 82 + // parseRepoParam parses a repo parameter in 'did/repoName' or bare 'did:...' format 83 + // and returns the full repository path on disk 84 func (x *Xrpc) parseRepoParam(repo string) (string, error) { 85 if repo == "" { 86 return "", xrpcerr.NewXrpcError( ··· 89 ) 90 } 91 92 + if strings.HasPrefix(repo, "did:") && !strings.Contains(repo, "/") { 93 + repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repo) 94 + if err != nil { 95 + return "", xrpcerr.RepoNotFoundError 96 + } 97 + return repoPath, nil 98 + } 99 + 100 parts := strings.SplitN(repo, "/", 2) 101 + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { 102 return "", xrpcerr.NewXrpcError( 103 xrpcerr.WithTag("InvalidRequest"), 104 xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"), 105 ) 106 } 107 108 + repoPath, _, err := x.Db.ResolveRepoOnDisk(x.Config.Repo.ScanPath, parts[0], parts[1]) 109 if err != nil { 110 return "", xrpcerr.RepoNotFoundError 111 } 112 return repoPath, nil 113 } 114
+7 -2
lexicons/git/refUpdate.json
··· 11 "required": [ 12 "ref", 13 "committerDid", 14 - "repoDid", 15 "repoName", 16 "oldSha", 17 "newSha", ··· 29 "description": "did of the user that pushed this ref", 30 "format": "did" 31 }, 32 "repoDid": { 33 "type": "string", 34 - "description": "did of the owner of the repo", 35 "format": "did" 36 }, 37 "repoName": {
··· 11 "required": [ 12 "ref", 13 "committerDid", 14 + "ownerDid", 15 "repoName", 16 "oldSha", 17 "newSha", ··· 29 "description": "did of the user that pushed this ref", 30 "format": "did" 31 }, 32 + "ownerDid": { 33 + "type": "string", 34 + "description": "did of the owner of the repo", 35 + "format": "did" 36 + }, 37 "repoDid": { 38 "type": "string", 39 + "description": "DID of the repo itself, if assigned", 40 "format": "did" 41 }, 42 "repoName": {
+5
lexicons/pipeline/pipeline.json
··· 77 "type": "string", 78 "format": "did" 79 }, 80 "repo": { 81 "type": "string" 82 },
··· 77 "type": "string", 78 "format": "did" 79 }, 80 + "repoDid": { 81 + "type": "string", 82 + "description": "DID of the repo itself, if assigned", 83 + "format": "did" 84 + }, 85 "repo": { 86 "type": "string" 87 },
+2 -1
lexicons/repo/repo.json
··· 62 }, 63 "repoDid": { 64 "type": "string", 65 - "format": "did" 66 }, 67 "createdAt": { 68 "type": "string",
··· 62 }, 63 "repoDid": { 64 "type": "string", 65 + "format": "did", 66 + "description": "DID of the repo itself, if assigned" 67 }, 68 "createdAt": { 69 "type": "string",