From ea86c2b56afeca6450a3e1081c0fd417a09e5434 Mon Sep 17 00:00:00 2001
From: zeripath <art27@cantab.net>
Date: Sun, 4 Dec 2022 17:48:18 +0000
Subject: [PATCH] Use GhostUser if needed for TrackedTimes (#22021)

When getting tracked times out of the db and loading their attributes
handle not exist errors in a nicer way. (Also prevent an NPE.)

Fix #22006

Signed-off-by: Andrew Thornton <art27@cantab.net>
---
 models/issues/tracked_time.go | 53 ++++++++++++++++++++---------------
 modules/convert/issue.go      | 11 ++++----
 2 files changed, 36 insertions(+), 28 deletions(-)

diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go
index 2de63a3a45..554b01bd40 100644
--- a/models/issues/tracked_time.go
+++ b/models/issues/tracked_time.go
@@ -5,6 +5,7 @@ package issues
 
 import (
 	"context"
+	"errors"
 	"time"
 
 	"code.gitea.io/gitea/models/db"
@@ -46,33 +47,41 @@ func (t *TrackedTime) LoadAttributes() (err error) {
 }
 
 func (t *TrackedTime) loadAttributes(ctx context.Context) (err error) {
+	// Load the issue
 	if t.Issue == nil {
 		t.Issue, err = GetIssueByID(ctx, t.IssueID)
-		if err != nil {
-			return
-		}
-		err = t.Issue.LoadRepo(ctx)
-		if err != nil {
-			return
-		}
-	}
-	if t.User == nil {
-		t.User, err = user_model.GetUserByID(ctx, t.UserID)
-		if err != nil {
-			return
-		}
-	}
-	return err
-}
-
-// LoadAttributes load Issue, User
-func (tl TrackedTimeList) LoadAttributes() (err error) {
-	for _, t := range tl {
-		if err = t.LoadAttributes(); err != nil {
+		if err != nil && !errors.Is(err, util.ErrNotExist) {
 			return err
 		}
 	}
-	return err
+	// Now load the repo for the issue (which we may have just loaded)
+	if t.Issue != nil {
+		err = t.Issue.LoadRepo(ctx)
+		if err != nil && !errors.Is(err, util.ErrNotExist) {
+			return err
+		}
+	}
+	// Load the user
+	if t.User == nil {
+		t.User, err = user_model.GetUserByID(ctx, t.UserID)
+		if err != nil {
+			if !errors.Is(err, util.ErrNotExist) {
+				return err
+			}
+			t.User = user_model.NewGhostUser()
+		}
+	}
+	return nil
+}
+
+// LoadAttributes load Issue, User
+func (tl TrackedTimeList) LoadAttributes() error {
+	for _, t := range tl {
+		if err := t.LoadAttributes(); err != nil {
+			return err
+		}
+	}
+	return nil
 }
 
 // FindTrackedTimesOptions represent the filters for tracked times. If an ID is 0 it will be ignored.
diff --git a/modules/convert/issue.go b/modules/convert/issue.go
index 221aeb885a..3bc1006507 100644
--- a/modules/convert/issue.go
+++ b/modules/convert/issue.go
@@ -110,12 +110,11 @@ func ToAPIIssueList(ctx context.Context, il issues_model.IssueList) []*api.Issue
 // ToTrackedTime converts TrackedTime to API format
 func ToTrackedTime(ctx context.Context, t *issues_model.TrackedTime) (apiT *api.TrackedTime) {
 	apiT = &api.TrackedTime{
-		ID:       t.ID,
-		IssueID:  t.IssueID,
-		UserID:   t.UserID,
-		UserName: t.User.Name,
-		Time:     t.Time,
-		Created:  t.Created,
+		ID:      t.ID,
+		IssueID: t.IssueID,
+		UserID:  t.UserID,
+		Time:    t.Time,
+		Created: t.Created,
 	}
 	if t.Issue != nil {
 		apiT.Issue = ToAPIIssue(ctx, t.Issue)