repos / git-pr

a self-hosted git collaboration server
git clone https://github.com/picosh/git-pr.git

commit
38474f6
parent
573fa67
author
Eric Bower
date
2024-10-21 21:07:55 -0400 EDT
feat: user-detail page
4 files changed,  +142, -14
M pr.go
M web.go
M pr.go
+7, -0
 1@@ -24,6 +24,7 @@ const (
 2 type GitPatchRequest interface {
 3 	GetUsers() ([]*User, error)
 4 	GetUserByID(userID int64) (*User, error)
 5+	GetUserByName(name string) (*User, error)
 6 	GetUserByPubkey(pubkey string) (*User, error)
 7 	UpsertUser(pubkey, name string) (*User, error)
 8 	IsBanned(pubkey, ipAddress string) error
 9@@ -79,6 +80,12 @@ func (pr PrCmd) GetUsers() ([]*User, error) {
10 	return users, err
11 }
12 
13+func (pr PrCmd) GetUserByName(name string) (*User, error) {
14+	var user User
15+	err := pr.Backend.DB.Get(&user, "SELECT * FROM app_users WHERE name=?", name)
16+	return &user, err
17+}
18+
19 func (pr PrCmd) GetUserByID(id int64) (*User, error) {
20 	var user User
21 	err := pr.Backend.DB.Get(&user, "SELECT * FROM app_users WHERE id=?", id)
M tmpl/pr-table.html
+3, -1
 1@@ -22,7 +22,9 @@
 2           <a href="{{.PrLink.Url}}">{{.PrLink.Text}}</a>
 3         </td>
 4         <td>
 5-          <code class="{{if .UserData.IsAdmin}}pill-admin{{end}}" title="{{.UserData.Pubkey}}">{{.UserData.Name}}</code>
 6+          <code class="{{if .UserData.IsAdmin}}pill-admin{{end}}" title="{{.UserData.Pubkey}}">
 7+            <a href="/users/{{.UserData.Name}}">{{.UserData.Name}}</a>
 8+          </code>
 9         </td>
10         <td><date>{{.Date}}</date></td>
11       </tr>
A tmpl/user-detail.html
+33, -0
 1@@ -0,0 +1,33 @@
 2+{{template "base" .}}
 3+
 4+{{define "title"}}{{.UserData.Name}} - user{{end}}
 5+
 6+{{define "meta"}}
 7+<link rel="alternate" type="application/atom+xml"
 8+      title="RSS feed for git collaboration server"
 9+      href="/users/{{.UserData.Name}}/rss" />
10+{{end}}
11+
12+{{define "body"}}
13+<header>
14+  <h1 class="text-2xl mb"><a href="/">home</a> / {{.UserData.Name}}</h1>
15+  <dl>
16+    <dt>ID</dt>
17+    <dd><code>#{{.UserData.UserID}}</code></dd>
18+
19+    <dt>Admin</dt>
20+    <dd>{{if .UserData.IsAdmin}}Yes{{else}}No{{end}}</dd>
21+
22+    <dt>Pubkey</dt>
23+    <dd><code>{{.UserData.Pubkey}}</code></dd>
24+  </dl>
25+</header>
26+
27+<main class="group">
28+  {{template "pr-table" .Prs}}
29+</main>
30+
31+<footer class="mt">
32+  <a href="/users/{{.UserData.Name}}/rss">rss</a>
33+</footer>
34+{{end}}
M web.go
+99, -13
  1@@ -101,6 +101,12 @@ type PrTableData struct {
  2 	MetaData
  3 }
  4 
  5+type UserDetailData struct {
  6+	Prs      []*PrListData
  7+	UserData UserData
  8+	MetaData
  9+}
 10+
 11 func createPrDataSorter(sort, sortDir string) func(a, b *PrListData) int {
 12 	return func(a *PrListData, b *PrListData) int {
 13 		if sort == "status" {
 14@@ -177,7 +183,7 @@ func getPrTableData(web *WebCtx, prs []*PatchRequest, query url.Values) ([]*PrLi
 15 			}
 16 
 17 			if username != "" {
 18-				if username != user.Name {
 19+				if username != strings.ToLower(user.Name) {
 20 					continue
 21 				}
 22 			}
 23@@ -258,9 +264,11 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
 24 }
 25 
 26 type UserData struct {
 27-	Name    string
 28-	IsAdmin bool
 29-	Pubkey  string
 30+	UserID    int64
 31+	Name      string
 32+	IsAdmin   bool
 33+	Pubkey    string
 34+	CreatedAt string
 35 }
 36 
 37 type MetaData struct {
 38@@ -279,6 +287,68 @@ type PrListData struct {
 39 	Status   string
 40 }
 41 
 42+func userDetailHandler(w http.ResponseWriter, r *http.Request) {
 43+	userName := r.PathValue("name")
 44+
 45+	web, err := getWebCtx(r)
 46+	if err != nil {
 47+		web.Logger.Error("fetch web", "err", err)
 48+		w.WriteHeader(http.StatusInternalServerError)
 49+		return
 50+	}
 51+
 52+	user, err := web.Pr.GetUserByName(userName)
 53+	if err != nil {
 54+		web.Logger.Error("cannot find user by name", "err", err, "name", userName)
 55+		w.WriteHeader(http.StatusNotFound)
 56+		return
 57+	}
 58+
 59+	pk, err := web.Backend.PubkeyToPublicKey(user.Pubkey)
 60+	if err != nil {
 61+		web.Logger.Error("cannot parse pubkey for pr user", "err", err)
 62+		w.WriteHeader(http.StatusUnprocessableEntity)
 63+		return
 64+	}
 65+	isAdmin := web.Backend.IsAdmin(pk)
 66+
 67+	prs, err := web.Pr.GetPatchRequests()
 68+	if err != nil {
 69+		web.Logger.Error("cannot get prs", "err", err)
 70+		w.WriteHeader(http.StatusInternalServerError)
 71+		return
 72+	}
 73+
 74+	query := r.URL.Query()
 75+	query.Set("user", userName)
 76+
 77+	prdata, err := getPrTableData(web, prs, query)
 78+	if err != nil {
 79+		web.Logger.Error("cannot get pr table data", "err", err)
 80+		w.WriteHeader(http.StatusInternalServerError)
 81+		return
 82+	}
 83+
 84+	w.Header().Set("content-type", "text/html")
 85+	tmpl := getTemplate("user-detail.html")
 86+	err = tmpl.Execute(w, UserDetailData{
 87+		Prs: prdata,
 88+		UserData: UserData{
 89+			UserID:    user.ID,
 90+			Name:      user.Name,
 91+			Pubkey:    user.Pubkey,
 92+			CreatedAt: user.CreatedAt.Format(time.RFC3339),
 93+			IsAdmin:   isAdmin,
 94+		},
 95+		MetaData: MetaData{
 96+			URL: web.Backend.Cfg.Url,
 97+		},
 98+	})
 99+	if err != nil {
100+		web.Backend.Logger.Error("cannot execute template", "err", err)
101+	}
102+}
103+
104 type RepoDetailData struct {
105 	ID        string
106 	CloneAddr string
107@@ -447,9 +517,11 @@ func prDetailHandler(w http.ResponseWriter, r *http.Request) {
108 			Patchset:    patchset,
109 			FormattedID: getFormattedPatchsetID(patchset.ID),
110 			UserData: UserData{
111-				Name:    user.Name,
112-				IsAdmin: web.Backend.IsAdmin(pk),
113-				Pubkey:  user.Pubkey,
114+				UserID:    user.ID,
115+				Name:      user.Name,
116+				IsAdmin:   web.Backend.IsAdmin(pk),
117+				Pubkey:    user.Pubkey,
118+				CreatedAt: user.CreatedAt.Format(time.RFC3339),
119 			},
120 			Date:      patchset.CreatedAt.Format(time.RFC3339),
121 			RangeDiff: rangeDiff,
122@@ -527,9 +599,11 @@ func prDetailHandler(w http.ResponseWriter, r *http.Request) {
123 			EventLog:            eventlog,
124 			FormattedPatchsetID: getFormattedPatchsetID(eventlog.PatchsetID.Int64),
125 			UserData: UserData{
126-				Name:    user.Name,
127-				IsAdmin: web.Backend.IsAdmin(pk),
128-				Pubkey:  user.Pubkey,
129+				UserID:    user.ID,
130+				Name:      user.Name,
131+				IsAdmin:   web.Backend.IsAdmin(pk),
132+				Pubkey:    user.Pubkey,
133+				CreatedAt: user.CreatedAt.Format(time.RFC3339),
134 			},
135 			Date: eventlog.CreatedAt.Format(web.Backend.Cfg.TimeFormat),
136 		})
137@@ -548,9 +622,11 @@ func prDetailHandler(w http.ResponseWriter, r *http.Request) {
138 		Pr: PrData{
139 			ID: pr.ID,
140 			UserData: UserData{
141-				Name:    user.Name,
142-				IsAdmin: isAdmin,
143-				Pubkey:  user.Pubkey,
144+				UserID:    user.ID,
145+				Name:      user.Name,
146+				IsAdmin:   isAdmin,
147+				Pubkey:    user.Pubkey,
148+				CreatedAt: user.CreatedAt.Format(time.RFC3339),
149 			},
150 			Title:  pr.Name,
151 			Date:   pr.CreatedAt.Format(web.Backend.Cfg.TimeFormat),
152@@ -588,6 +664,7 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
153 	id := r.PathValue("id")
154 	repoID := r.PathValue("repoid")
155 	pubkey := r.URL.Query().Get("pubkey")
156+	username := r.PathValue("user")
157 
158 	if id != "" {
159 		var prID int64
160@@ -604,6 +681,13 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
161 			return
162 		}
163 		eventLogs, err = web.Pr.GetEventLogsByUserID(user.ID)
164+	} else if username != "" {
165+		user, perr := web.Pr.GetUserByName(username)
166+		if perr != nil {
167+			w.WriteHeader(http.StatusNotFound)
168+			return
169+		}
170+		eventLogs, err = web.Pr.GetEventLogsByUserID(user.ID)
171 	} else if repoID != "" {
172 		eventLogs, err = web.Pr.GetEventLogsByRepoID(repoID)
173 	} else {
174@@ -790,6 +874,8 @@ func StartWebServer(cfg *GitCfg) {
175 	http.HandleFunc("GET /prs/{id}/rss", ctxMdw(ctx, rssHandler))
176 	http.HandleFunc("GET /repos/{id}", ctxMdw(ctx, repoDetailHandler))
177 	http.HandleFunc("GET /repos/{repoid}/rss", ctxMdw(ctx, rssHandler))
178+	http.HandleFunc("GET /users/{name}", ctxMdw(ctx, userDetailHandler))
179+	http.HandleFunc("GET /users/{name}/rss", ctxMdw(ctx, rssHandler))
180 	http.HandleFunc("GET /", ctxMdw(ctx, indexHandler))
181 	http.HandleFunc("GET /syntax.css", ctxMdw(ctx, chromaStyleHandler))
182 	http.HandleFunc("GET /rss", ctxMdw(ctx, rssHandler))