Monorepo for Tangled

appview, knotserver: add dual-mode repo DID addressing

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