appview: allows a default knot to be configured #858

open
opened by willdot.net targeting master from willdot.net/tangled-core: master

This follows on from the work carried out in #836

I've added a select box in the Knots settings page which pulls in the users knots and also adds in knot1.tangled.sh. When the user selects one of these options, it will save to their profile in the database. NOTE: I haven't yet implemented adding that to the AT record because I'm not sure on how the lexicon setup works yet!

Then when users go to create a new repo / fork, if there is a value in their profile for the default knot, then that will pre select the knot to use for the new repo / fork.

Changed files
+111 -6
appview
+7
appview/db/db.go
··· 1174 return err 1175 }) 1176 1177 return &DB{ 1178 db, 1179 logger,
··· 1174 return err 1175 }) 1176 1177 + runMigration(conn, logger, "add-default-knot-profile", func(tx *sql.Tx) error { 1178 + _, err := tx.Exec(` 1179 + alter table profile add column default_knot text; 1180 + `) 1181 + return err 1182 + }) 1183 + 1184 return &DB{ 1185 db, 1186 logger,
+11 -4
appview/db/profile.go
··· 130 description, 131 include_bluesky, 132 location, 133 - pronouns 134 ) 135 - values (?, ?, ?, ?, ?)`, 136 profile.Did, 137 profile.Description, 138 includeBskyValue, 139 profile.Location, 140 profile.Pronouns, 141 ) 142 143 if err != nil { ··· 311 func GetProfile(e Execer, did string) (*models.Profile, error) { 312 var profile models.Profile 313 var pronouns sql.Null[string] 314 315 profile.Did = did 316 317 includeBluesky := 0 318 319 err := e.QueryRow( 320 - `select description, include_bluesky, location, pronouns from profile where did = ?`, 321 did, 322 - ).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns) 323 if err == sql.ErrNoRows { 324 profile := models.Profile{} 325 profile.Did = did ··· 336 337 if pronouns.Valid { 338 profile.Pronouns = pronouns.V 339 } 340 341 rows, err := e.Query(`select link from profile_links where did = ?`, did)
··· 130 description, 131 include_bluesky, 132 location, 133 + pronouns, 134 + default_knot 135 ) 136 + values (?, ?, ?, ?, ?, ?)`, 137 profile.Did, 138 profile.Description, 139 includeBskyValue, 140 profile.Location, 141 profile.Pronouns, 142 + profile.DefaultKnot, 143 ) 144 145 if err != nil { ··· 313 func GetProfile(e Execer, did string) (*models.Profile, error) { 314 var profile models.Profile 315 var pronouns sql.Null[string] 316 + var defaultKnot sql.Null[string] 317 318 profile.Did = did 319 320 includeBluesky := 0 321 322 err := e.QueryRow( 323 + `select description, include_bluesky, location, pronouns, default_knot from profile where did = ?`, 324 did, 325 + ).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns, &defaultKnot) 326 if err == sql.ErrNoRows { 327 profile := models.Profile{} 328 profile.Did = did ··· 339 340 if pronouns.Valid { 341 profile.Pronouns = pronouns.V 342 + } 343 + 344 + if defaultKnot.Valid { 345 + profile.DefaultKnot = defaultKnot.V 346 } 347 348 rows, err := e.Query(`select link from profile_links where did = ?`, did)
+10
appview/knots/knots.go
··· 80 return 81 } 82 83 k.Pages.Knots(w, pages.KnotsParams{ 84 LoggedInUser: user, 85 Registrations: registrations, 86 Tabs: knotsTabs, 87 Tab: "knots", 88 }) 89 } 90
··· 80 return 81 } 82 83 + defaultKnot := "" 84 + profile, err := db.GetProfile(k.Db, user.Did) 85 + if err != nil { 86 + k.Logger.Warn("gettings user profile to get default knot", "error", err) 87 + } 88 + if profile != nil { 89 + defaultKnot = profile.DefaultKnot 90 + } 91 + 92 k.Pages.Knots(w, pages.KnotsParams{ 93 LoggedInUser: user, 94 Registrations: registrations, 95 Tabs: knotsTabs, 96 Tab: "knots", 97 + DefaultKnot: defaultKnot, 98 }) 99 } 100
+1
appview/models/profile.go
··· 20 Stats [2]VanityStat 21 PinnedRepos [6]syntax.ATURI 22 Pronouns string 23 } 24 25 func (p Profile) IsLinksEmpty() bool {
··· 20 Stats [2]VanityStat 21 PinnedRepos [6]syntax.ATURI 22 Pronouns string 23 + DefaultKnot string 24 } 25 26 func (p Profile) IsLinksEmpty() bool {
+3
appview/pages/pages.go
··· 409 Registrations []models.Registration 410 Tabs []map[string]any 411 Tab string 412 } 413 414 func (p *Pages) Knots(w io.Writer, params KnotsParams) error { ··· 474 type NewRepoParams struct { 475 LoggedInUser *oauth.User 476 Knots []string 477 } 478 479 func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error { ··· 484 LoggedInUser *oauth.User 485 Knots []string 486 RepoInfo repoinfo.RepoInfo 487 } 488 489 func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error {
··· 409 Registrations []models.Registration 410 Tabs []map[string]any 411 Tab string 412 + DefaultKnot string 413 } 414 415 func (p *Pages) Knots(w io.Writer, params KnotsParams) error { ··· 475 type NewRepoParams struct { 476 LoggedInUser *oauth.User 477 Knots []string 478 + DefaultKnot string 479 } 480 481 func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error { ··· 486 LoggedInUser *oauth.User 487 Knots []string 488 RepoInfo repoinfo.RepoInfo 489 + DefaultKnot string 490 } 491 492 func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error {
+24
appview/pages/templates/knots/index.html
··· 31 <div class="flex flex-col gap-6"> 32 {{ block "list" . }} {{ end }} 33 {{ block "register" . }} {{ end }} 34 </div> 35 </section> 36 {{ end }} ··· 59 {{ end }} 60 </div> 61 <div id="operation-error" class="text-red-500 dark:text-red-400"></div> 62 </section> 63 {{ end }} 64
··· 31 <div class="flex flex-col gap-6"> 32 {{ block "list" . }} {{ end }} 33 {{ block "register" . }} {{ end }} 34 + {{ block "default-knot" . }} {{ end }} 35 </div> 36 </section> 37 {{ end }} ··· 60 {{ end }} 61 </div> 62 <div id="operation-error" class="text-red-500 dark:text-red-400"></div> 63 + </section> 64 + {{ end }} 65 + 66 + {{ define "default-knot" }} 67 + <section class="rounded w-full flex flex-col gap-2"> 68 + <h2 class="text-sm font-bold py-2 uppercase dark:text-gray-300">default knot</h2> 69 + <select id="default-knot" name="default-knot" 70 + class="p-1 max-w-64 border border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700" 71 + hx-post="/profile/default-knot" 72 + hx-swap="none" 73 + name="default-knot"> 74 + <option value="" > 75 + Choose a default Knot 76 + </option> 77 + <option value="knot1.tangled.sh" {{if eq $.DefaultKnot "knot1.tangled.sh"}}selected{{end}} > 78 + knot1.tangled.sh 79 + </option> 80 + {{ range $registration := .Registrations }} 81 + <option value="{{ .Domain }}" class="py-1" {{if eq $.DefaultKnot .Domain}}selected{{end}}> 82 + {{ .Domain }} 83 + </option> 84 + {{ end }} 85 + </select> 86 </section> 87 {{ end }} 88
+3 -1
appview/pages/templates/repo/fork.html
··· 25 value="{{ . }}" 26 class="mr-2" 27 id="domain-{{ . }}" 28 - {{if eq (len $.Knots) 1}}checked{{end}} 29 /> 30 <label for="domain-{{ . }}" class="dark:text-white">{{ . }}</label> 31 </div>
··· 25 value="{{ . }}" 26 class="mr-2" 27 id="domain-{{ . }}" 28 + {{if eq (len $.Knots) 1}}checked 29 + {{else if eq $.DefaultKnot . }}checked 30 + {{end}} 31 /> 32 <label for="domain-{{ . }}" class="dark:text-white">{{ . }}</label> 33 </div>
+3 -1
appview/pages/templates/repo/new.html
··· 155 class="mr-2" 156 id="domain-{{ . }}" 157 required 158 - {{if eq (len $.Knots) 1}}checked{{end}} 159 /> 160 <label for="domain-{{ . }}" class="dark:text-white lowercase">{{ . }}</label> 161 </div>
··· 155 class="mr-2" 156 id="domain-{{ . }}" 157 required 158 + {{if eq (len $.Knots) 1}}checked 159 + {{else if eq $.DefaultKnot . }}checked 160 + {{end}} 161 /> 162 <label for="domain-{{ . }}" class="dark:text-white lowercase">{{ . }}</label> 163 </div>
+10
appview/repo/repo.go
··· 1003 return 1004 } 1005 1006 rp.pages.ForkRepo(w, pages.ForkRepoParams{ 1007 LoggedInUser: user, 1008 Knots: knots, 1009 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 1010 }) 1011 1012 case http.MethodPost:
··· 1003 return 1004 } 1005 1006 + defaultKnot := "" 1007 + profile, err := db.GetProfile(rp.db, user.Did) 1008 + if err != nil { 1009 + rp.logger.Warn("gettings user profile to get default knot", "error", err) 1010 + } 1011 + if profile != nil { 1012 + defaultKnot = profile.DefaultKnot 1013 + } 1014 + 1015 rp.pages.ForkRepo(w, pages.ForkRepoParams{ 1016 LoggedInUser: user, 1017 Knots: knots, 1018 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 1019 + DefaultKnot: defaultKnot, 1020 }) 1021 1022 case http.MethodPost:
+28
appview/state/profile.go
··· 613 s.updateProfile(profile, w, r) 614 } 615 616 func (s *State) updateProfile(profile *models.Profile, w http.ResponseWriter, r *http.Request) { 617 user := s.oauth.GetUser(r) 618 tx, err := s.db.BeginTx(r.Context(), nil)
··· 613 s.updateProfile(profile, w, r) 614 } 615 616 + func (s *State) UpdateProfileDefaultKnot(w http.ResponseWriter, r *http.Request) { 617 + err := r.ParseForm() 618 + if err != nil { 619 + log.Println("invalid profile update form", err) 620 + return 621 + } 622 + user := s.oauth.GetUser(r) 623 + 624 + profile, err := db.GetProfile(s.db, user.Did) 625 + if err != nil { 626 + log.Printf("getting profile data for %s: %s", user.Did, err) 627 + } 628 + 629 + profile.DefaultKnot = r.Form.Get("default-knot") 630 + 631 + tx, err := s.db.BeginTx(r.Context(), nil) 632 + if err != nil { 633 + log.Println("failed to start transaction", err) 634 + return 635 + } 636 + 637 + err = db.UpsertProfile(tx, profile) 638 + if err != nil { 639 + log.Println("failed to update profile", err) 640 + return 641 + } 642 + } 643 + 644 func (s *State) updateProfile(profile *models.Profile, w http.ResponseWriter, r *http.Request) { 645 user := s.oauth.GetUser(r) 646 tx, err := s.db.BeginTx(r.Context(), nil)
+1
appview/state/router.go
··· 162 r.Get("/edit-pins", s.EditPinsFragment) 163 r.Post("/bio", s.UpdateProfileBio) 164 r.Post("/pins", s.UpdateProfilePins) 165 }) 166 167 r.Mount("/settings", s.SettingsRouter())
··· 162 r.Get("/edit-pins", s.EditPinsFragment) 163 r.Post("/bio", s.UpdateProfileBio) 164 r.Post("/pins", s.UpdateProfilePins) 165 + r.Post("/default-knot", s.UpdateProfileDefaultKnot) 166 }) 167 168 r.Mount("/settings", s.SettingsRouter())
+10
appview/state/state.go
··· 453 return 454 } 455 456 s.pages.NewRepo(w, pages.NewRepoParams{ 457 LoggedInUser: user, 458 Knots: knots, 459 }) 460 461 case http.MethodPost:
··· 453 return 454 } 455 456 + defaultKnot := "" 457 + profile, err := db.GetProfile(s.db, user.Did) 458 + if err != nil { 459 + s.logger.Warn("gettings user profile to get default knot", "error", err) 460 + } 461 + if profile != nil { 462 + defaultKnot = profile.DefaultKnot 463 + } 464 + 465 s.pages.NewRepo(w, pages.NewRepoParams{ 466 LoggedInUser: user, 467 Knots: knots, 468 + DefaultKnot: defaultKnot, 469 }) 470 471 case http.MethodPost: