repos / git-pr

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

commit
088df6f
parent
e76ab98
author
Eric Bower
date
2024-06-24 22:55:45 -0400 EDT
code passes
5 files changed,  +222, -72
M cli.go
M db.go
M pr.go
M web.go
M backend.go
+2, -2
1@@ -44,6 +44,6 @@ func (be *Backend) IsAdmin(pk ssh.PublicKey) bool {
2 	return false
3 }
4 
5-func (be *Backend) IsPrOwner(pka, pkb string) bool {
6-	return be.KeysEqual(pka, pkb)
7+func (be *Backend) IsPrOwner(pka, pkb int64) bool {
8+	return pka == pkb
9 }
M cli.go
+69, -13
  1@@ -42,6 +42,7 @@ Here's how it works:
  2 	- Owner marks PR as accepted and pushes code to main (git-push)`
  3 
  4 	pubkey := be.Pubkey(sesh.PublicKey())
  5+	userName := sesh.User()
  6 	app := &cli.App{
  7 		Name:        "ssh",
  8 		Description: desc,
  9@@ -108,13 +109,16 @@ Here's how it works:
 10 				},
 11 				Action: func(cCtx *cli.Context) error {
 12 					pubkey := be.Pubkey(sesh.PublicKey())
 13+					user, err := pr.GetUserByPubkey(pubkey)
 14+					if err != nil {
 15+						return err
 16+					}
 17 					isPubkey := cCtx.Bool("pubkey")
 18 					prID := cCtx.Int64("pr")
 19 					repoID := cCtx.String("repo")
 20 					var eventLogs []*EventLog
 21-					var err error
 22 					if isPubkey {
 23-						eventLogs, err = pr.GetEventLogsByPubkey(pubkey)
 24+						eventLogs, err = pr.GetEventLogsByUserID(user.ID)
 25 					} else if prID != 0 {
 26 						eventLogs, err = pr.GetEventLogsByPrID(prID)
 27 					} else if repoID != "" {
 28@@ -212,8 +216,16 @@ Here's how it works:
 29 						Args:      true,
 30 						ArgsUsage: "[repoID]",
 31 						Action: func(cCtx *cli.Context) error {
 32+							user, err := pr.UpsertUser(&User{
 33+								Pubkey: pubkey,
 34+								Name:   userName,
 35+							})
 36+							if err != nil {
 37+								return err
 38+							}
 39+
 40 							repoID := cCtx.Args().First()
 41-							request, err := pr.SubmitPatchRequest(repoID, pubkey, sesh)
 42+							request, err := pr.SubmitPatchRequest(repoID, user.ID, sesh)
 43 							if err != nil {
 44 								return err
 45 							}
 46@@ -440,7 +452,15 @@ Here's how it works:
 47 								return fmt.Errorf("PR has already been accepted")
 48 							}
 49 
 50-							err = pr.UpdatePatchRequestStatus(prID, pubkey, "accepted")
 51+							user, err := pr.UpsertUser(&User{
 52+								Pubkey: pubkey,
 53+								Name:   userName,
 54+							})
 55+							if err != nil {
 56+								return err
 57+							}
 58+
 59+							err = pr.UpdatePatchRequestStatus(prID, user.ID, "accepted")
 60 							if err == nil {
 61 								wish.Printf(sesh, "Accepted PR %s (#%d)\n", patchReq.Name, patchReq.ID)
 62 							}
 63@@ -457,12 +477,21 @@ Here's how it works:
 64 							if err != nil {
 65 								return err
 66 							}
 67+
 68+							user, err := pr.UpsertUser(&User{
 69+								Pubkey: pubkey,
 70+								Name:   userName,
 71+							})
 72+							if err != nil {
 73+								return err
 74+							}
 75+
 76 							patchReq, err := pr.GetPatchRequestByID(prID)
 77 							if err != nil {
 78 								return err
 79 							}
 80 							pk := sesh.PublicKey()
 81-							isContrib := be.Pubkey(pk) == patchReq.Pubkey
 82+							isContrib := be.Pubkey(pk) == user.Pubkey
 83 							isAdmin := be.IsAdmin(pk)
 84 							if !isAdmin && !isContrib {
 85 								return fmt.Errorf("you are not authorized to change PR status")
 86@@ -472,7 +501,7 @@ Here's how it works:
 87 								return fmt.Errorf("PR has already been closed")
 88 							}
 89 
 90-							err = pr.UpdatePatchRequestStatus(prID, pubkey, "closed")
 91+							err = pr.UpdatePatchRequestStatus(prID, user.ID, "closed")
 92 							if err == nil {
 93 								wish.Printf(sesh, "Closed PR %s (#%d)\n", patchReq.Name, patchReq.ID)
 94 							}
 95@@ -489,12 +518,22 @@ Here's how it works:
 96 							if err != nil {
 97 								return err
 98 							}
 99+
100 							patchReq, err := pr.GetPatchRequestByID(prID)
101 							if err != nil {
102 								return err
103 							}
104+
105+							user, err := pr.UpsertUser(&User{
106+								Pubkey: pubkey,
107+								Name:   userName,
108+							})
109+							if err != nil {
110+								return err
111+							}
112+
113 							pk := sesh.PublicKey()
114-							isContrib := be.Pubkey(pk) == patchReq.Pubkey
115+							isContrib := be.Pubkey(pk) == user.Pubkey
116 							isAdmin := be.IsAdmin(pk)
117 							if !isAdmin && !isContrib {
118 								return fmt.Errorf("you are not authorized to change PR status")
119@@ -504,7 +543,7 @@ Here's how it works:
120 								return fmt.Errorf("PR is already open")
121 							}
122 
123-							err = pr.UpdatePatchRequestStatus(prID, pubkey, "open")
124+							err = pr.UpdatePatchRequestStatus(prID, user.ID, "open")
125 							if err == nil {
126 								wish.Printf(sesh, "Reopened PR %s (#%d)\n", patchReq.Name, patchReq.ID)
127 							}
128@@ -525,8 +564,17 @@ Here's how it works:
129 							if err != nil {
130 								return err
131 							}
132+
133+							user, err := pr.UpsertUser(&User{
134+								Pubkey: pubkey,
135+								Name:   userName,
136+							})
137+							if err != nil {
138+								return err
139+							}
140+
141 							isAdmin := be.IsAdmin(sesh.PublicKey())
142-							isPrOwner := be.IsPrOwner(prq.Pubkey, be.Pubkey(sesh.PublicKey()))
143+							isPrOwner := be.IsPrOwner(prq.UserID, user.ID)
144 							if !isAdmin && !isPrOwner {
145 								return fmt.Errorf("unauthorized, you are not the owner of this PR")
146 							}
147@@ -539,7 +587,7 @@ Here's how it works:
148 
149 							err = pr.UpdatePatchRequestName(
150 								prID,
151-								be.Pubkey(sesh.PublicKey()),
152+								user.ID,
153 								title,
154 							)
155 							if err == nil {
156@@ -574,10 +622,18 @@ Here's how it works:
157 								return err
158 							}
159 
160+							user, err := pr.UpsertUser(&User{
161+								Pubkey: pubkey,
162+								Name:   userName,
163+							})
164+							if err != nil {
165+								return err
166+							}
167+
168 							isAdmin := be.IsAdmin(sesh.PublicKey())
169 							isReview := cCtx.Bool("review")
170 							isReplace := cCtx.Bool("force")
171-							isPrOwner := be.IsPrOwner(prq.Pubkey, be.Pubkey(sesh.PublicKey()))
172+							isPrOwner := be.IsPrOwner(prq.UserID, user.ID)
173 							if !isAdmin && !isPrOwner {
174 								return fmt.Errorf("unauthorized, you are not the owner of this PR")
175 							}
176@@ -591,7 +647,7 @@ Here's how it works:
177 								op = OpReplace
178 							}
179 
180-							patches, err := pr.SubmitPatchSet(prID, pubkey, op, sesh)
181+							patches, err := pr.SubmitPatchSet(prID, user.ID, op, sesh)
182 							if err != nil {
183 								return err
184 							}
185@@ -603,7 +659,7 @@ Here's how it works:
186 
187 							reviewTxt := ""
188 							if isReview {
189-								err = pr.UpdatePatchRequestStatus(prID, pubkey, "reviewed")
190+								err = pr.UpdatePatchRequestStatus(prID, user.ID, "reviewed")
191 								if err != nil {
192 									return err
193 								}
M db.go
+39, -11
  1@@ -8,10 +8,18 @@ import (
  2 	_ "modernc.org/sqlite"
  3 )
  4 
  5+type User struct {
  6+	ID        int64     `db:"id"`
  7+	Pubkey    string    `db:"pubkey"`
  8+	Name      string    `db:"name"`
  9+	CreatedAt time.Time `db:"created_at"`
 10+	UpdatedAt time.Time `db:"updated_at"`
 11+}
 12+
 13 // PatchRequest is a database model for patches submitted to a Repo.
 14 type PatchRequest struct {
 15 	ID        int64     `db:"id"`
 16-	Pubkey    string    `db:"pubkey"`
 17+	UserID    int64     `db:"user_id"`
 18 	RepoID    string    `db:"repo_id"`
 19 	Name      string    `db:"name"`
 20 	Text      string    `db:"text"`
 21@@ -24,7 +32,7 @@ type PatchRequest struct {
 22 // This usually corresponds to a git commit.
 23 type Patch struct {
 24 	ID             int64     `db:"id"`
 25-	Pubkey         string    `db:"pubkey"`
 26+	UserID         int64     `db:"user_id"`
 27 	PatchRequestID int64     `db:"patch_request_id"`
 28 	AuthorName     string    `db:"author_name"`
 29 	AuthorEmail    string    `db:"author_email"`
 30@@ -42,7 +50,7 @@ type Patch struct {
 31 // EventLog is a event log for RSS or other notification systems.
 32 type EventLog struct {
 33 	ID             int64     `db:"id"`
 34-	Pubkey         string    `db:"pubkey"`
 35+	UserID         int64     `db:"user_id"`
 36 	RepoID         string    `db:"repo_id"`
 37 	PatchRequestID int64     `db:"patch_request_id"`
 38 	Event          string    `db:"event"`
 39@@ -57,20 +65,32 @@ type DB struct {
 40 }
 41 
 42 var schema = `
 43-CREATE TABLE IF NOT EXISTS patch_requests (
 44+CREATE TABLE IF NOT EXISTS app_users (
 45   id INTEGER PRIMARY KEY AUTOINCREMENT,
 46   pubkey TEXT NOT NULL,
 47+  name TEXT NOT NULL,
 48+  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 49+  updated_at DATETIME NOT NULL
 50+);
 51+
 52+CREATE TABLE IF NOT EXISTS patch_requests (
 53+  id INTEGER PRIMARY KEY AUTOINCREMENT,
 54+  user_id INTEGER NOT NULL,
 55   repo_id TEXT NOT NULL,
 56   name TEXT NOT NULL,
 57   text TEXT NOT NULL,
 58   status TEXT NOT NULL,
 59   created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 60-  updated_at DATETIME NOT NULL
 61+  updated_at DATETIME NOT NULL,
 62+  CONSTRAINT pr_user_id_fk
 63+    FOREIGN KEY(user_id) REFERENCES app_users(id)
 64+    ON DELETE CASCADE
 65+    ON UPDATE CASCADE
 66 );
 67 
 68 CREATE TABLE IF NOT EXISTS patches (
 69   id INTEGER PRIMARY KEY AUTOINCREMENT,
 70-  pubkey TEXT NOT NULL,
 71+  user_id INTEGER NOT NULL,
 72   patch_request_id INTEGER NOT NULL,
 73   author_name TEXT NOT NULL,
 74   author_email TEXT NOT NULL,
 75@@ -84,14 +104,18 @@ CREATE TABLE IF NOT EXISTS patches (
 76   raw_text TEXT NOT NULL,
 77   created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 78   CONSTRAINT pr_id_fk
 79-  FOREIGN KEY(patch_request_id) REFERENCES patch_requests(id)
 80-  ON DELETE CASCADE
 81-  ON UPDATE CASCADE
 82+    FOREIGN KEY(patch_request_id) REFERENCES patch_requests(id)
 83+    ON DELETE CASCADE
 84+    ON UPDATE CASCADE,
 85+  CONSTRAINT patches_user_id_fk
 86+    FOREIGN KEY(user_id) REFERENCES app_users(id)
 87+    ON DELETE CASCADE
 88+    ON UPDATE CASCADE
 89 );
 90 
 91 CREATE TABLE IF NOT EXISTS event_logs (
 92   id INTEGER PRIMARY KEY AUTOINCREMENT,
 93-  pubkey TEXT NOT NULL,
 94+  user_id INTEGER NOT NULL,
 95   repo_id TEXT,
 96   patch_request_id INTEGER,
 97   event TEXT NOT NULL,
 98@@ -100,7 +124,11 @@ CREATE TABLE IF NOT EXISTS event_logs (
 99   CONSTRAINT event_logs_pr_id_fk
100   FOREIGN KEY(patch_request_id) REFERENCES patch_requests(id)
101   ON DELETE CASCADE
102-  ON UPDATE CASCADE
103+  ON UPDATE CASCADE,
104+  CONSTRAINT event_logs_user_id_fk
105+    FOREIGN KEY(user_id) REFERENCES app_users(id)
106+    ON DELETE CASCADE
107+    ON UPDATE CASCADE
108 );
109 `
110 
M pr.go
+65, -26
  1@@ -24,23 +24,28 @@ const (
  2 )
  3 
  4 type GitPatchRequest interface {
  5+	GetUsers() ([]*User, error)
  6+	GetUserByID(userID int64) (*User, error)
  7+	GetUserByPubkey(pubkey string) (*User, error)
  8+	CreateUser(user *User) (*User, error)
  9+	UpsertUser(user *User) (*User, error)
 10 	GetRepos() ([]*Repo, error)
 11 	GetReposWithLatestPr() ([]RepoWithLatestPr, error)
 12 	GetRepoByID(repoID string) (*Repo, error)
 13-	SubmitPatchRequest(repoID string, pubkey string, patchset io.Reader) (*PatchRequest, error)
 14-	SubmitPatchSet(prID int64, pubkey string, op PatchsetOp, patchset io.Reader) ([]*Patch, error)
 15+	SubmitPatchRequest(repoID string, userID int64, patchset io.Reader) (*PatchRequest, error)
 16+	SubmitPatchSet(prID, userID int64, op PatchsetOp, patchset io.Reader) ([]*Patch, error)
 17 	GetPatchRequestByID(prID int64) (*PatchRequest, error)
 18 	GetPatchRequests() ([]*PatchRequest, error)
 19 	GetPatchRequestsByRepoID(repoID string) ([]*PatchRequest, error)
 20 	GetPatchesByPrID(prID int64) ([]*Patch, error)
 21-	UpdatePatchRequestStatus(prID int64, pubkey, status string) error
 22-	UpdatePatchRequestName(prID int64, pubkey, name string) error
 23+	UpdatePatchRequestStatus(prID, userID int64, status string) error
 24+	UpdatePatchRequestName(prID, userID int64, name string) error
 25 	DeletePatchesByPrID(prID int64) error
 26 	CreateEventLog(eventLog EventLog) error
 27 	GetEventLogs() ([]*EventLog, error)
 28 	GetEventLogsByRepoID(repoID string) ([]*EventLog, error)
 29 	GetEventLogsByPrID(prID int64) ([]*EventLog, error)
 30-	GetEventLogsByPubkey(pubkey string) ([]*EventLog, error)
 31+	GetEventLogsByUserID(userID int64) ([]*EventLog, error)
 32 }
 33 
 34 type PrCmd struct {
 35@@ -50,6 +55,26 @@ type PrCmd struct {
 36 var _ GitPatchRequest = PrCmd{}
 37 var _ GitPatchRequest = (*PrCmd)(nil)
 38 
 39+func (pr PrCmd) GetUsers() ([]*User, error) {
 40+	return []*User{}, nil
 41+}
 42+
 43+func (pr PrCmd) GetUserByID(id int64) (*User, error) {
 44+	return nil, nil
 45+}
 46+
 47+func (pr PrCmd) GetUserByPubkey(pubkey string) (*User, error) {
 48+	return nil, nil
 49+}
 50+
 51+func (pr PrCmd) CreateUser(user *User) (*User, error) {
 52+	return nil, nil
 53+}
 54+
 55+func (pr PrCmd) UpsertUser(user *User) (*User, error) {
 56+	return nil, nil
 57+}
 58+
 59 type PrWithRepo struct {
 60 	LastUpdatedPrID int64
 61 	RepoID          string
 62@@ -57,6 +82,7 @@ type PrWithRepo struct {
 63 
 64 type RepoWithLatestPr struct {
 65 	*Repo
 66+	User         *User
 67 	PatchRequest *PatchRequest
 68 }
 69 
 70@@ -72,11 +98,24 @@ func (pr PrCmd) GetReposWithLatestPr() ([]RepoWithLatestPr, error) {
 71 		return repos, err
 72 	}
 73 
 74+	users, err := pr.GetUsers()
 75+	if err != nil {
 76+		return repos, err
 77+	}
 78+
 79 	// we want recently modified repos to be on top
 80 	for _, prq := range prs {
 81 		for _, repo := range pr.Backend.Cfg.Repos {
 82 			if prq.RepoID == repo.ID {
 83+				var user *User
 84+				for _, usr := range users {
 85+					if prq.UserID == usr.ID {
 86+						user = usr
 87+						break
 88+					}
 89+				}
 90 				repos = append(repos, RepoWithLatestPr{
 91+					User:         user,
 92 					Repo:         repo,
 93 					PatchRequest: &prq,
 94 				})
 95@@ -162,14 +201,14 @@ func (cmd PrCmd) GetPatchRequestByID(prID int64) (*PatchRequest, error) {
 96 }
 97 
 98 // Status types: open, closed, accepted, reviewed.
 99-func (cmd PrCmd) UpdatePatchRequestStatus(prID int64, pubkey string, status string) error {
100+func (cmd PrCmd) UpdatePatchRequestStatus(prID int64, userID int64, status string) error {
101 	_, err := cmd.Backend.DB.Exec(
102 		"UPDATE patch_requests SET status=? WHERE id=?",
103 		status,
104 		prID,
105 	)
106 	_ = cmd.CreateEventLog(EventLog{
107-		Pubkey:         pubkey,
108+		UserID:         userID,
109 		PatchRequestID: prID,
110 		Event:          "pr_status_changed",
111 		Data:           fmt.Sprintf(`{"status":"%s"}`, status),
112@@ -177,7 +216,7 @@ func (cmd PrCmd) UpdatePatchRequestStatus(prID int64, pubkey string, status stri
113 	return err
114 }
115 
116-func (cmd PrCmd) UpdatePatchRequestName(prID int64, pubkey string, name string) error {
117+func (cmd PrCmd) UpdatePatchRequestName(prID int64, userID int64, name string) error {
118 	if name == "" {
119 		return fmt.Errorf("must provide name or text in order to update patch request")
120 	}
121@@ -188,7 +227,7 @@ func (cmd PrCmd) UpdatePatchRequestName(prID int64, pubkey string, name string)
122 		prID,
123 	)
124 	_ = cmd.CreateEventLog(EventLog{
125-		Pubkey:         pubkey,
126+		UserID:         userID,
127 		PatchRequestID: prID,
128 		Event:          "pr_name_changed",
129 		Data:           fmt.Sprintf(`{"name":"%s"}`, name),
130@@ -215,8 +254,8 @@ func (cmd PrCmd) CreateEventLog(eventLog EventLog) error {
131 	}
132 
133 	_, err := cmd.Backend.DB.Exec(
134-		"INSERT INTO event_logs (pubkey, repo_id, patch_request_id, event, data) VALUES (?, ?, ?, ?, ?)",
135-		eventLog.Pubkey,
136+		"INSERT INTO event_logs (user_id, repo_id, patch_request_id, event, data) VALUES (?, ?, ?, ?, ?)",
137+		eventLog.UserID,
138 		eventLog.RepoID,
139 		eventLog.PatchRequestID,
140 		eventLog.Event,
141@@ -322,8 +361,8 @@ func (cmd PrCmd) createPatch(tx *sqlx.Tx, review bool, patch *Patch) (int64, err
142 
143 	var patchID int64
144 	row := tx.QueryRow(
145-		"INSERT INTO patches (pubkey, patch_request_id, author_name, author_email, author_date, title, body, body_appendix, commit_sha, content_sha, review, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id",
146-		patch.Pubkey,
147+		"INSERT INTO patches (user_id, patch_request_id, author_name, author_email, author_date, title, body, body_appendix, commit_sha, content_sha, review, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id",
148+		patch.UserID,
149 		patch.PatchRequestID,
150 		patch.AuthorName,
151 		patch.AuthorEmail,
152@@ -346,7 +385,7 @@ func (cmd PrCmd) createPatch(tx *sqlx.Tx, review bool, patch *Patch) (int64, err
153 	return patchID, err
154 }
155 
156-func (cmd PrCmd) SubmitPatchRequest(repoID string, pubkey string, patchset io.Reader) (*PatchRequest, error) {
157+func (cmd PrCmd) SubmitPatchRequest(repoID string, userID int64, patchset io.Reader) (*PatchRequest, error) {
158 	tx, err := cmd.Backend.DB.Beginx()
159 	if err != nil {
160 		return nil, err
161@@ -369,8 +408,8 @@ func (cmd PrCmd) SubmitPatchRequest(repoID string, pubkey string, patchset io.Re
162 
163 	var prID int64
164 	row := tx.QueryRow(
165-		"INSERT INTO patch_requests (pubkey, repo_id, name, text, status, updated_at) VALUES(?, ?, ?, ?, ?, ?) RETURNING id",
166-		pubkey,
167+		"INSERT INTO patch_requests (user_id, repo_id, name, text, status, updated_at) VALUES(?, ?, ?, ?, ?, ?) RETURNING id",
168+		userID,
169 		repoID,
170 		prName,
171 		prText,
172@@ -386,7 +425,7 @@ func (cmd PrCmd) SubmitPatchRequest(repoID string, pubkey string, patchset io.Re
173 	}
174 
175 	for _, patch := range patches {
176-		patch.Pubkey = pubkey
177+		patch.UserID = userID
178 		patch.PatchRequestID = prID
179 		_, err = cmd.createPatch(tx, false, patch)
180 		if err != nil {
181@@ -400,7 +439,7 @@ func (cmd PrCmd) SubmitPatchRequest(repoID string, pubkey string, patchset io.Re
182 	}
183 
184 	_ = cmd.CreateEventLog(EventLog{
185-		Pubkey:         pubkey,
186+		UserID:         userID,
187 		PatchRequestID: prID,
188 		Event:          "pr_created",
189 	})
190@@ -410,7 +449,7 @@ func (cmd PrCmd) SubmitPatchRequest(repoID string, pubkey string, patchset io.Re
191 	return &pr, err
192 }
193 
194-func (cmd PrCmd) SubmitPatchSet(prID int64, pubkey string, op PatchsetOp, patchset io.Reader) ([]*Patch, error) {
195+func (cmd PrCmd) SubmitPatchSet(prID int64, userID int64, op PatchsetOp, patchset io.Reader) ([]*Patch, error) {
196 	fin := []*Patch{}
197 	tx, err := cmd.Backend.DB.Beginx()
198 	if err != nil {
199@@ -434,7 +473,7 @@ func (cmd PrCmd) SubmitPatchSet(prID int64, pubkey string, op PatchsetOp, patchs
200 	}
201 
202 	for _, patch := range patches {
203-		patch.Pubkey = pubkey
204+		patch.UserID = userID
205 		patch.PatchRequestID = prID
206 		patchID, err := cmd.createPatch(tx, op == OpReview, patch)
207 		if err == nil {
208@@ -461,7 +500,7 @@ func (cmd PrCmd) SubmitPatchSet(prID int64, pubkey string, op PatchsetOp, patchs
209 		}
210 
211 		_ = cmd.CreateEventLog(EventLog{
212-			Pubkey:         pubkey,
213+			UserID:         userID,
214 			PatchRequestID: prID,
215 			Event:          event,
216 		})
217@@ -506,19 +545,19 @@ func (cmd PrCmd) GetEventLogsByPrID(prID int64) ([]*EventLog, error) {
218 	return eventLogs, err
219 }
220 
221-func (cmd PrCmd) GetEventLogsByPubkey(pubkey string) ([]*EventLog, error) {
222+func (cmd PrCmd) GetEventLogsByUserID(userID int64) ([]*EventLog, error) {
223 	eventLogs := []*EventLog{}
224 	query := `SELECT * FROM event_logs
225-	WHERE pubkey=?
226+	WHERE user_id=?
227 		OR patch_request_id IN (
228-			SELECT id FROM patch_requests WHERE pubkey=?
229+			SELECT id FROM patch_requests WHERE user_id=?
230 		)
231 	ORDER BY created_at DESC`
232 	err := cmd.Backend.DB.Select(
233 		&eventLogs,
234 		query,
235-		pubkey,
236-		pubkey,
237+		userID,
238+		userID,
239 	)
240 	return eventLogs, err
241 }
M web.go
+47, -20
  1@@ -112,8 +112,9 @@ func repoListHandler(w http.ResponseWriter, r *http.Request) {
  2 		if repo.PatchRequest != nil {
  3 			curpr := repo.PatchRequest
  4 			ls = &PrListData{
  5-				ID:     curpr.ID,
  6-				Pubkey: curpr.Pubkey,
  7+				ID:       curpr.ID,
  8+				UserName: repo.User.Name,
  9+				Pubkey:   repo.User.Pubkey,
 10 				LinkData: LinkData{
 11 					Url:  template.URL(fmt.Sprintf("/prs/%d", curpr.ID)),
 12 					Text: curpr.Name,
 13@@ -146,10 +147,11 @@ func repoListHandler(w http.ResponseWriter, r *http.Request) {
 14 
 15 type PrListData struct {
 16 	LinkData
 17-	ID     int64
 18-	Pubkey string
 19-	Date   string
 20-	Status string
 21+	ID       int64
 22+	UserName string
 23+	Pubkey   string
 24+	Date     string
 25+	Status   string
 26 }
 27 
 28 type RepoDetailData struct {
 29@@ -191,9 +193,14 @@ func repoDetailHandler(w http.ResponseWriter, r *http.Request) {
 30 	acceptedList := []PrListData{}
 31 	closedList := []PrListData{}
 32 	for _, curpr := range prs {
 33+		user, err := web.Pr.GetUserByID(curpr.UserID)
 34+		if err != nil {
 35+			continue
 36+		}
 37 		ls := PrListData{
 38-			ID:     curpr.ID,
 39-			Pubkey: curpr.Pubkey,
 40+			ID:       curpr.ID,
 41+			UserName: user.Name,
 42+			Pubkey:   user.Pubkey,
 43 			LinkData: LinkData{
 44 				Url:  template.URL(fmt.Sprintf("/prs/%d", curpr.ID)),
 45 				Text: curpr.Name,
 46@@ -221,6 +228,7 @@ func repoDetailHandler(w http.ResponseWriter, r *http.Request) {
 47 		OpenPrs:     openList,
 48 		AcceptedPrs: acceptedList,
 49 		ClosedPrs:   closedList,
 50+		ReviewedPrs: reviewedList,
 51 	})
 52 	if err != nil {
 53 		fmt.Println(err)
 54@@ -228,11 +236,12 @@ func repoDetailHandler(w http.ResponseWriter, r *http.Request) {
 55 }
 56 
 57 type PrData struct {
 58-	ID     int64
 59-	Title  string
 60-	Date   string
 61-	Pubkey string
 62-	Status string
 63+	ID       int64
 64+	Title    string
 65+	Date     string
 66+	UserName string
 67+	Pubkey   string
 68+	Status   string
 69 }
 70 
 71 type PatchData struct {
 72@@ -299,6 +308,12 @@ func prDetailHandler(w http.ResponseWriter, r *http.Request) {
 73 		})
 74 	}
 75 
 76+	user, err := web.Pr.GetUserByID(pr.UserID)
 77+	if err != nil {
 78+		w.WriteHeader(http.StatusNotFound)
 79+		return
 80+	}
 81+
 82 	w.Header().Set("content-type", "text/html")
 83 	tmpl := getTemplate("pr-detail.html")
 84 	err = tmpl.Execute(w, PrHeaderData{
 85@@ -310,11 +325,12 @@ func prDetailHandler(w http.ResponseWriter, r *http.Request) {
 86 		Branch:  repo.DefaultBranch,
 87 		Patches: patchesData,
 88 		Pr: PrData{
 89-			ID:     pr.ID,
 90-			Title:  pr.Name,
 91-			Pubkey: pr.Pubkey,
 92-			Date:   pr.CreatedAt.Format(time.RFC3339),
 93-			Status: pr.Status,
 94+			ID:       pr.ID,
 95+			Title:    pr.Name,
 96+			UserName: user.Name,
 97+			Pubkey:   user.Pubkey,
 98+			Date:     pr.CreatedAt.Format(time.RFC3339),
 99+			Status:   pr.Status,
100 		},
101 	})
102 	if err != nil {
103@@ -345,6 +361,11 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
104 	id := r.PathValue("id")
105 	repoID := r.PathValue("repoid")
106 	pubkey := r.URL.Query().Get("pubkey")
107+	user, err := web.Pr.GetUserByPubkey(pubkey)
108+	if err != nil {
109+		w.WriteHeader(http.StatusNotFound)
110+		return
111+	}
112 	if id != "" {
113 		var prID int64
114 		prID, err = getPrID(id)
115@@ -354,7 +375,7 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
116 		}
117 		eventLogs, err = web.Pr.GetEventLogsByPrID(prID)
118 	} else if pubkey != "" {
119-		eventLogs, err = web.Pr.GetEventLogsByPubkey(pubkey)
120+		eventLogs, err = web.Pr.GetEventLogsByUserID(user.ID)
121 	} else if repoID != "" {
122 		eventLogs, err = web.Pr.GetEventLogsByRepoID(repoID)
123 	} else {
124@@ -382,6 +403,12 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
125 		if err != nil {
126 			continue
127 		}
128+
129+		user, err := web.Pr.GetUserByID(pr.UserID)
130+		if err != nil {
131+			continue
132+		}
133+
134 		title := fmt.Sprintf(
135 			`%s in %s for PR "%s" (#%d)`,
136 			eventLog.Event,
137@@ -396,7 +423,7 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
138 			Content:     content,
139 			Created:     eventLog.CreatedAt,
140 			Description: title,
141-			Author:      &feeds.Author{Name: eventLog.Pubkey},
142+			Author:      &feeds.Author{Name: user.Name},
143 		}
144 
145 		feedItems = append(feedItems, item)