Monorepo for Tangled tangled.org

show branches in a dropdown

Changed files
+293 -25
appview
db
pages
templates
repo
pulls
randomart
state
+1 -7
appview/db/pulls.go
··· 83 83 return pullAt, err 84 84 } 85 85 86 - func GetPullId(e Execer, repoAt syntax.ATURI) (int, error) { 86 + func NextPullId(e Execer, repoAt syntax.ATURI) (int, error) { 87 87 var pullId int 88 88 err := e.QueryRow(`select next_pull_id from repo_pull_seqs where repo_at = ?`, repoAt).Scan(&pullId) 89 89 return pullId - 1, err 90 - } 91 - 92 - func GetPullOwnerDid(e Execer, repoAt syntax.ATURI, pullId int) (string, error) { 93 - var ownerDid string 94 - err := e.QueryRow(`select owner_did from pulls where repo_at = ? and pull_id = ?`, repoAt, pullId).Scan(&ownerDid) 95 - return ownerDid, err 96 90 } 97 91 98 92 func GetPulls(e Execer, repoAt syntax.ATURI) ([]Pull, error) {
+1
appview/pages/pages.go
··· 509 509 type RepoNewPullParams struct { 510 510 LoggedInUser *auth.User 511 511 RepoInfo RepoInfo 512 + Branches []types.Branch 512 513 Active string 513 514 } 514 515
+10 -1
appview/pages/templates/repo/pulls/new.html
··· 15 15 <p class="text-gray-500"> 16 16 The branch you want to make your change against. 17 17 </p> 18 - <input type="text" name="targetBranch" id="targetBranch" /> 18 + <select class="p-1 border border-gray-200 bg-white"> 19 + <option disabled selected>Select a branch</option> 20 + {{ range .Branches }} 21 + <option 22 + value="{{ .Reference.Name }}" 23 + class="py-1"> 24 + {{ .Reference.Name }} 25 + </option> 26 + {{ end }} 27 + </select> 19 28 </div> 20 29 <div> 21 30 <label for="body">body</label>
-2
appview/pages/templates/settings.html
··· 12 12 {{ end }} 13 13 14 14 {{ define "profile" }} 15 - <<<<<<< HEAD 16 15 <h2 class="text-sm font-bold py-2 px-6 uppercase">profile</h2> 17 16 <section class="rounded bg-white drop-shadow-sm px-6 py-4 mb-6 w-full lg:w-fit"> 18 17 <dl class="grid grid-cols-[auto_1fr] gap-x-4"> ··· 29 28 {{ end }} 30 29 31 30 {{ define "keys" }} 32 - <<<<<<< HEAD 33 31 <h2 class="text-sm font-bold py-2 px-6 uppercase">ssh keys</h2> 34 32 <section class="rounded bg-white drop-shadow-sm px-6 py-4 mb-6 w-full lg:w-fit"> 35 33 <div id="key-list" class="flex flex-col gap-6 mb-8">
+173
appview/randomart/randomart.go
··· 1 + package randomart 2 + 3 + /* 4 + * Draw an ASCII-Art representing the fingerprint so human brain can 5 + * profit from its built-in pattern recognition ability. 6 + * This technique is called "random art" and can be found in some 7 + * scientific publications like this original paper: 8 + * 9 + * "Hash Visualization: a New Technique to improve Real-World Security", 10 + * Perrig A. and Song D., 1999, International Workshop on Cryptographic 11 + * Techniques and E-Commerce (CrypTEC '99) 12 + * sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf 13 + * 14 + * The subject came up in a talk by Dan Kaminsky, too. 15 + * 16 + * If you see the picture is different, the key is different. 17 + * If the picture looks the same, you still know nothing. 18 + * 19 + * The algorithm used here is a worm crawling over a discrete plane, 20 + * leaving a trace (augmenting the field) everywhere it goes. 21 + * Movement is taken from dgst_raw 2bit-wise. Bumping into walls 22 + * makes the respective movement vector be ignored for this turn. 23 + * Graphs are not unambiguous, because circles in graphs can be 24 + * walked in either direction. 25 + */ 26 + 27 + import ( 28 + "os" 29 + ) 30 + 31 + func MAX(a, b int) int { 32 + if a > b { 33 + return a 34 + } 35 + return b 36 + } 37 + 38 + func MIN(a, b int) int { 39 + if a < b { 40 + return a 41 + } 42 + return b 43 + } 44 + 45 + /* 46 + * Field sizes for the random art. Have to be odd, so the starting point 47 + * can be in the exact middle of the picture, and FLDBASE should be >=8 . 48 + * Else pictures would be too dense, and drawing the frame would 49 + * fail, too, because the key type would not fit in anymore. 50 + */ 51 + const ( 52 + FLDBASE = 8 53 + FLDSIZE_Y = (FLDBASE + 1) 54 + FLDSIZE_X = (FLDBASE*2 + 1) 55 + ) 56 + 57 + func FromString(str string) string { 58 + ch := make(chan byte) 59 + 60 + go func() { 61 + defer close(ch) 62 + for _, v := range []byte(str) { 63 + ch <- v 64 + } 65 + }() 66 + return key_fingerprint_randomart(ch) 67 + } 68 + 69 + func FromFile(file *os.File) string { 70 + ch := make(chan byte) 71 + 72 + go func() { 73 + defer close(ch) 74 + // TODO make input a 1 element byte array 75 + input := make([]byte, 1) 76 + nread, err := file.Read(input) 77 + for err == nil && nread > 0 { 78 + ch <- input[0] 79 + nread, err = file.Read(input) 80 + } 81 + }() 82 + return key_fingerprint_randomart(ch) 83 + } 84 + 85 + func key_fingerprint_randomart(ch chan byte) string { 86 + /* 87 + * Chars to be used after each other every time the worm 88 + * intersects with itself. Matter of taste. 89 + */ 90 + augment_string := " .o+=*BOX@%&#/^SE" 91 + var field [FLDSIZE_X][FLDSIZE_Y]byte 92 + len_aug := len(augment_string) - 1 93 + var retval [(FLDSIZE_X + 3) * (FLDSIZE_Y + 2)]byte 94 + 95 + /* initialize field */ 96 + x := FLDSIZE_X / 2 97 + y := FLDSIZE_Y / 2 98 + 99 + /* process raw key */ 100 + for input, ok := <-ch; ok; input, ok = <-ch { 101 + /* each byte conveys four 2-bit move commands */ 102 + for b := 0; b < 4; b++ { 103 + /* evaluate 2 bit, rest is shifted later */ 104 + if input&0x1 > 0 { 105 + x += 1 106 + } else { 107 + x += -1 108 + } 109 + 110 + if input&0x2 > 0 { 111 + y++ 112 + } else { 113 + y-- 114 + } 115 + 116 + /* assure we are still in bounds */ 117 + x = MAX(x, 0) 118 + y = MAX(y, 0) 119 + x = MIN(x, FLDSIZE_X-1) 120 + y = MIN(y, FLDSIZE_Y-1) 121 + 122 + /* augment the field */ 123 + if int(field[x][y]) < len_aug-2 { 124 + field[x][y]++ 125 + } 126 + input = input >> 2 127 + } 128 + } 129 + 130 + /* mark starting point and end point*/ 131 + field[FLDSIZE_X/2][FLDSIZE_Y/2] = byte(len_aug - 1) 132 + field[x][y] = byte(len_aug) 133 + 134 + i := 0 135 + retval[i] = '+' 136 + i++ 137 + 138 + /* output upper border */ 139 + for x := 0; x < FLDSIZE_X; x++ { 140 + retval[i] = '-' 141 + i++ 142 + } 143 + retval[i] = '+' 144 + i++ 145 + retval[i] = '\n' 146 + i++ 147 + 148 + /* output content */ 149 + for y := 0; y < FLDSIZE_Y; y++ { 150 + retval[i] = '|' 151 + i++ 152 + for x := 0; x < FLDSIZE_X; x++ { 153 + retval[i] = augment_string[MIN(int(field[x][y]), len_aug)] 154 + i++ 155 + } 156 + retval[i] = '|' 157 + i++ 158 + retval[i] = '\n' 159 + i++ 160 + } 161 + 162 + /* output lower border */ 163 + retval[i] = '+' 164 + i++ 165 + for j := 0; j < FLDSIZE_X; j++ { 166 + retval[i] = '-' 167 + i++ 168 + } 169 + retval[i] = '+' 170 + i++ 171 + 172 + return string(retval[0:i]) 173 + }
+41 -12
appview/state/repo.go
··· 35 35 log.Println("failed to fully resolve repo", err) 36 36 return 37 37 } 38 - protocol := "http" 39 - if !s.config.Dev { 40 - protocol = "https" 41 - } 42 38 43 - var reqUrl string 44 - if ref != "" { 45 - reqUrl = fmt.Sprintf("%s://%s/%s/%s/tree/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref) 46 - } else { 47 - reqUrl = fmt.Sprintf("%s://%s/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName) 39 + us, err := NewUnsignedClient(f.Knot, s.config.Dev) 40 + if err != nil { 41 + log.Printf("failed to create unsigned client for %s", f.Knot) 42 + s.pages.Error503(w) 43 + return 48 44 } 49 45 50 - resp, err := http.Get(reqUrl) 46 + resp, err := us.Index(f.OwnerDid(), f.RepoName, ref) 51 47 if err != nil { 52 48 s.pages.Error503(w) 53 49 log.Println("failed to reach knotserver", err) ··· 303 299 304 300 switch r.Method { 305 301 case http.MethodGet: 302 + us, err := NewUnsignedClient(f.Knot, s.config.Dev) 303 + if err != nil { 304 + log.Printf("failed to create unsigned client for %s", f.Knot) 305 + s.pages.Error503(w) 306 + return 307 + } 308 + 309 + resp, err := us.Branches(f.OwnerDid(), f.RepoName) 310 + if err != nil { 311 + log.Println("failed to reach knotserver", err) 312 + return 313 + } 314 + 315 + body, err := io.ReadAll(resp.Body) 316 + if err != nil { 317 + log.Printf("Error reading response body: %v", err) 318 + return 319 + } 320 + 321 + var result types.RepoBranchesResponse 322 + err = json.Unmarshal(body, &result) 323 + if err != nil { 324 + log.Println("failed to parse response:", err) 325 + return 326 + } 327 + 306 328 s.pages.RepoNewPull(w, pages.RepoNewPullParams{ 307 329 LoggedInUser: user, 308 330 RepoInfo: f.RepoInfo(s, user), 331 + Branches: result.Branches, 309 332 }) 310 333 case http.MethodPost: 311 334 title := r.FormValue("title") ··· 347 370 return 348 371 } 349 372 client, _ := s.auth.AuthorizedClient(r) 350 - pullId, err := db.GetPullId(s.db, f.RepoAt) 373 + pullId, err := db.NextPullId(s.db, f.RepoAt) 351 374 if err != nil { 352 375 log.Println("failed to get pull id", err) 353 376 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") ··· 604 627 return 605 628 } 606 629 607 - resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/branches", f.Knot, f.OwnerDid(), f.RepoName)) 630 + us, err := NewUnsignedClient(f.Knot, s.config.Dev) 631 + if err != nil { 632 + log.Println("failed to create unsigned client", err) 633 + return 634 + } 635 + 636 + resp, err := us.Branches(f.OwnerDid(), f.RepoName) 608 637 if err != nil { 609 638 log.Println("failed to reach knotserver", err) 610 639 return
+67 -3
appview/state/signer.go
··· 41 41 }, 42 42 } 43 43 44 - uri := "https" 44 + scheme := "https" 45 45 if dev { 46 - uri = "http" 46 + scheme = "http" 47 47 } 48 - url, err := url.Parse(fmt.Sprintf("%s://%s", uri, domain)) 48 + url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain)) 49 49 if err != nil { 50 50 return nil, err 51 51 } ··· 193 193 194 194 return s.client.Do(req) 195 195 } 196 + 197 + type UnsignedClient struct { 198 + Url *url.URL 199 + client *http.Client 200 + } 201 + 202 + func NewUnsignedClient(domain string, dev bool) (*UnsignedClient, error) { 203 + client := &http.Client{ 204 + Timeout: 5 * time.Second, 205 + } 206 + 207 + scheme := "https" 208 + if dev { 209 + scheme = "http" 210 + } 211 + url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, domain)) 212 + if err != nil { 213 + return nil, err 214 + } 215 + 216 + unsignedClient := &UnsignedClient{ 217 + client: client, 218 + Url: url, 219 + } 220 + 221 + return unsignedClient, nil 222 + } 223 + 224 + func (us *UnsignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) { 225 + return http.NewRequest(method, us.Url.JoinPath(endpoint).String(), bytes.NewReader(body)) 226 + } 227 + 228 + func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*http.Response, error) { 229 + const ( 230 + Method = "GET" 231 + ) 232 + 233 + endpoint := fmt.Sprintf("/%s/%s/tree/%s", ownerDid, repoName, ref) 234 + if ref == "" { 235 + endpoint = fmt.Sprintf("/%s/%s", ownerDid, repoName) 236 + } 237 + 238 + req, err := us.newRequest(Method, endpoint, nil) 239 + if err != nil { 240 + return nil, err 241 + } 242 + 243 + return us.client.Do(req) 244 + } 245 + 246 + func (us *UnsignedClient) Branches(ownerDid, repoName string) (*http.Response, error) { 247 + const ( 248 + Method = "GET" 249 + ) 250 + 251 + endpoint := fmt.Sprintf("/%s/%s/branches", ownerDid, repoName) 252 + 253 + req, err := us.newRequest(Method, endpoint, nil) 254 + if err != nil { 255 + return nil, err 256 + } 257 + 258 + return us.client.Do(req) 259 + }