- 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
+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)
+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>
+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))