tenseleyflow/shithub / ea1ba9f

Browse files

S32: dispatch settings via principals + thread SettingsActive=danger

Authored by espadonne
SHA
ea1ba9ffedc15fa8209cc5b63f8c2f1f8d9068fe
Parents
15bfd2c
Tree
9b9a47d

1 changed file

StatusFile+-
M internal/web/handlers/repo/lifecycle.go 45 10
internal/web/handlers/repo/lifecycle.gomodified
@@ -12,6 +12,7 @@ import (
1212
 	"github.com/jackc/pgx/v5/pgtype"
1313
 
1414
 	"github.com/tenseleyFlow/shithub/internal/auth/policy"
15
+	"github.com/tenseleyFlow/shithub/internal/orgs"
1516
 	"github.com/tenseleyFlow/shithub/internal/repos/lifecycle"
1617
 	reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
1718
 	"github.com/tenseleyFlow/shithub/internal/web/middleware"
@@ -54,18 +55,51 @@ func (h *Handlers) MountLifecycle(r chi.Router) {
5455
 // loadRepoAndAuthorize is the common preamble for every settings-route
5556
 // handler. Resolves owner+repo, applies policy.Can with the chosen
5657
 // action, and either returns the row or writes the response.
58
+//
59
+// Owner kind dispatch goes through principals (S30) so org-owned
60
+// repos resolve through the same path as user-owned ones. The
61
+// returned `usersdb.User` is the OWNING USER for user-owned repos,
62
+// or a synthetic row carrying just `ID` + `Username` (= the org slug)
63
+// for org-owned repos — handlers that re-construct paths only need
64
+// the slug, and the few that need a real users row already short-
65
+// circuit via `viewer`.
5766
 func (h *Handlers) loadRepoAndAuthorize(w http.ResponseWriter, r *http.Request, action policy.Action) (reposdb.Repo, usersdb.User, bool) {
5867
 	ownerName := chi.URLParam(r, "owner")
5968
 	repoName := chi.URLParam(r, "repo")
60
-	owner, err := h.uq.GetUserByUsername(r.Context(), h.d.Pool, ownerName)
69
+	principal, err := orgs.Resolve(r.Context(), h.d.Pool, ownerName)
6170
 	if err != nil {
6271
 		h.d.Render.HTTPError(w, r, http.StatusNotFound, "")
6372
 		return reposdb.Repo{}, usersdb.User{}, false
6473
 	}
65
-	row, err := h.rq.GetRepoByOwnerUserAndName(r.Context(), h.d.Pool, reposdb.GetRepoByOwnerUserAndNameParams{
66
-		OwnerUserID: pgtype.Int8{Int64: owner.ID, Valid: true},
67
-		Name:        repoName,
68
-	})
74
+	var (
75
+		row    reposdb.Repo
76
+		owner  usersdb.User
77
+	)
78
+	switch principal.Kind {
79
+	case orgs.PrincipalUser:
80
+		owner, err = h.uq.GetUserByID(r.Context(), h.d.Pool, principal.ID)
81
+		if err != nil {
82
+			h.d.Render.HTTPError(w, r, http.StatusNotFound, "")
83
+			return reposdb.Repo{}, usersdb.User{}, false
84
+		}
85
+		row, err = h.rq.GetRepoByOwnerUserAndName(r.Context(), h.d.Pool, reposdb.GetRepoByOwnerUserAndNameParams{
86
+			OwnerUserID: pgtype.Int8{Int64: owner.ID, Valid: true},
87
+			Name:        repoName,
88
+		})
89
+	case orgs.PrincipalOrg:
90
+		row, err = h.rq.GetRepoByOwnerOrgAndName(r.Context(), h.d.Pool, reposdb.GetRepoByOwnerOrgAndNameParams{
91
+			OwnerOrgID: pgtype.Int8{Int64: principal.ID, Valid: true},
92
+			Name:       repoName,
93
+		})
94
+		// Synthesize an owner with the slug so callers that read
95
+		// `owner.Username` for path composition still work. ID is
96
+		// the org id; the field is repurposed but no caller treats
97
+		// it as a user id in path-only contexts.
98
+		owner = usersdb.User{ID: principal.ID, Username: principal.Slug}
99
+	default:
100
+		h.d.Render.HTTPError(w, r, http.StatusNotFound, "")
101
+		return reposdb.Repo{}, usersdb.User{}, false
102
+	}
69103
 	if err != nil {
70104
 		h.d.Render.HTTPError(w, r, http.StatusNotFound, "")
71105
 		return reposdb.Repo{}, usersdb.User{}, false
@@ -96,11 +130,12 @@ func (h *Handlers) repoSettings(w http.ResponseWriter, r *http.Request) {
96130
 	}
97131
 	transfers, _ := h.rq.ListTransfersForRepo(r.Context(), h.d.Pool, row.ID)
98132
 	h.d.Render.RenderPage(w, r, "repo/settings", map[string]any{
99
-		"Title":     "Settings · " + row.Name,
100
-		"CSRFToken": middleware.CSRFTokenForRequest(r),
101
-		"Owner":     owner.Username,
102
-		"Repo":      row,
103
-		"Transfers": transfers,
133
+		"Title":          "Settings · " + row.Name,
134
+		"CSRFToken":      middleware.CSRFTokenForRequest(r),
135
+		"Owner":          owner.Username,
136
+		"Repo":           row,
137
+		"Transfers":      transfers,
138
+		"SettingsActive": "danger",
104139
 	})
105140
 }
106141