forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

Compare changes

Choose any two refs to compare.

Changed files
+226
api
knotserver
lexicons
+51
api/tangled/repolistRepos.go
··· 1 + // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 + 3 + package tangled 4 + 5 + // schema: sh.tangled.repo.listRepos 6 + 7 + import ( 8 + "context" 9 + 10 + "github.com/bluesky-social/indigo/lex/util" 11 + ) 12 + 13 + const ( 14 + RepoListReposNSID = "sh.tangled.repo.listRepos" 15 + ) 16 + 17 + // RepoListRepos_Output is the output of a sh.tangled.repo.listRepos call. 18 + type RepoListRepos_Output struct { 19 + Users []*RepoListRepos_User `json:"users" cborgen:"users"` 20 + } 21 + 22 + // RepoListRepos_RepoEntry is a "repoEntry" in the sh.tangled.repo.listRepos schema. 23 + type RepoListRepos_RepoEntry struct { 24 + // defaultBranch: Default branch of the repository 25 + DefaultBranch *string `json:"defaultBranch,omitempty" cborgen:"defaultBranch,omitempty"` 26 + // did: DID of the repository owner 27 + Did string `json:"did" cborgen:"did"` 28 + // fullPath: Full path to the repository 29 + FullPath string `json:"fullPath" cborgen:"fullPath"` 30 + // name: Repository name 31 + Name string `json:"name" cborgen:"name"` 32 + } 33 + 34 + // RepoListRepos_User is a "user" in the sh.tangled.repo.listRepos schema. 35 + type RepoListRepos_User struct { 36 + // did: DID of the user 37 + Did string `json:"did" cborgen:"did"` 38 + Repos []*RepoListRepos_RepoEntry `json:"repos" cborgen:"repos"` 39 + } 40 + 41 + // RepoListRepos calls the XRPC method "sh.tangled.repo.listRepos". 42 + func RepoListRepos(ctx context.Context, c util.LexClient) (*RepoListRepos_Output, error) { 43 + var out RepoListRepos_Output 44 + 45 + params := map[string]interface{}{} 46 + if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.listRepos", params, nil, &out); err != nil { 47 + return nil, err 48 + } 49 + 50 + return &out, nil 51 + }
+103
knotserver/xrpc/list_repos.go
··· 1 + package xrpc 2 + 3 + import ( 4 + "net/http" 5 + "os" 6 + "path/filepath" 7 + "strings" 8 + 9 + securejoin "github.com/cyphar/filepath-securejoin" 10 + "tangled.org/core/api/tangled" 11 + "tangled.org/core/knotserver/git" 12 + xrpcerr "tangled.org/core/xrpc/errors" 13 + ) 14 + 15 + // ListRepos lists all users (DIDs) and their repositories by scanning the repository directory 16 + func (x *Xrpc) ListRepos(w http.ResponseWriter, r *http.Request) { 17 + scanPath := x.Config.Repo.ScanPath 18 + 19 + didEntries, err := os.ReadDir(scanPath) 20 + if err != nil { 21 + x.Logger.Error("failed to read scan path", "error", err, "path", scanPath) 22 + writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 23 + return 24 + } 25 + 26 + var users []*tangled.RepoListRepos_User 27 + 28 + for _, didEntry := range didEntries { 29 + if !didEntry.IsDir() { 30 + continue 31 + } 32 + 33 + did := didEntry.Name() 34 + 35 + // Validate DID format (basic check) 36 + if !strings.HasPrefix(did, "did:") { 37 + continue 38 + } 39 + 40 + didPath, err := securejoin.SecureJoin(scanPath, did) 41 + if err != nil { 42 + x.Logger.Warn("failed to join path for did", "did", did, "error", err) 43 + continue 44 + } 45 + 46 + // Read repositories for this DID 47 + repoEntries, err := os.ReadDir(didPath) 48 + if err != nil { 49 + x.Logger.Warn("failed to read did directory", "did", did, "error", err) 50 + continue 51 + } 52 + 53 + var repos []*tangled.RepoListRepos_RepoEntry 54 + 55 + for _, repoEntry := range repoEntries { 56 + if !repoEntry.IsDir() { 57 + continue 58 + } 59 + 60 + repoName := repoEntry.Name() 61 + 62 + // Check if it's a valid git repository 63 + repoPath, err := securejoin.SecureJoin(didPath, repoName) 64 + if err != nil { 65 + continue 66 + } 67 + 68 + repo, err := git.PlainOpen(repoPath) 69 + if err != nil { 70 + // Not a valid git repository, skip 71 + continue 72 + } 73 + 74 + // Get default branch 75 + defaultBranch := "master" 76 + branch, err := repo.FindMainBranch() 77 + if err == nil { 78 + defaultBranch = branch 79 + } 80 + 81 + repos = append(repos, &tangled.RepoListRepos_RepoEntry{ 82 + Name: repoName, 83 + Did: did, 84 + FullPath: filepath.Join(did, repoName), 85 + DefaultBranch: &defaultBranch, 86 + }) 87 + } 88 + 89 + // Only add user if they have repositories 90 + if len(repos) > 0 { 91 + users = append(users, &tangled.RepoListRepos_User{ 92 + Did: did, 93 + Repos: repos, 94 + }) 95 + } 96 + } 97 + 98 + response := tangled.RepoListRepos_Output{ 99 + Users: users, 100 + } 101 + 102 + writeJson(w, response) 103 + }
+1
knotserver/xrpc/xrpc.go
··· 73 73 74 74 // service query endpoints (no auth required) 75 75 r.Get("/"+tangled.OwnerNSID, x.Owner) 76 + r.Get("/"+tangled.RepoListReposNSID, x.ListRepos) 76 77 77 78 return r 78 79 }
+71
lexicons/repo/listRepos.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.tangled.repo.listRepos", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Lists all users (DIDs) and their repositories", 8 + "parameters": { 9 + "type": "params", 10 + "properties": {} 11 + }, 12 + "output": { 13 + "encoding": "application/json", 14 + "schema": { 15 + "type": "object", 16 + "required": ["users"], 17 + "properties": { 18 + "users": { 19 + "type": "array", 20 + "items": { 21 + "type": "ref", 22 + "ref": "#user" 23 + } 24 + } 25 + } 26 + } 27 + } 28 + }, 29 + "user": { 30 + "type": "object", 31 + "required": ["did", "repos"], 32 + "properties": { 33 + "did": { 34 + "type": "string", 35 + "format": "did", 36 + "description": "DID of the user" 37 + }, 38 + "repos": { 39 + "type": "array", 40 + "items": { 41 + "type": "ref", 42 + "ref": "#repoEntry" 43 + } 44 + } 45 + } 46 + }, 47 + "repoEntry": { 48 + "type": "object", 49 + "required": ["name", "did", "fullPath"], 50 + "properties": { 51 + "name": { 52 + "type": "string", 53 + "description": "Repository name" 54 + }, 55 + "did": { 56 + "type": "string", 57 + "format": "did", 58 + "description": "DID of the repository owner" 59 + }, 60 + "fullPath": { 61 + "type": "string", 62 + "description": "Full path to the repository" 63 + }, 64 + "defaultBranch": { 65 + "type": "string", 66 + "description": "Default branch of the repository" 67 + } 68 + } 69 + } 70 + } 71 + }