Monorepo for Tangled tangled.org

appview/issues: use middleware to extract issue

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li a0079caa d88e0be0

verified
Changed files
+47 -87
appview
issues
+47 -87
appview/issues/issues.go
··· 1 1 package issues 2 2 3 3 import ( 4 + "context" 5 + "database/sql" 6 + "errors" 4 7 "fmt" 5 8 "log" 6 - mathrand "math/rand/v2" 9 + "log/slog" 7 10 "net/http" 8 11 "slices" 9 - "strconv" 10 12 "strings" 11 13 "time" 12 14 13 15 comatproto "github.com/bluesky-social/indigo/api/atproto" 14 - "github.com/bluesky-social/indigo/atproto/data" 16 + "github.com/bluesky-social/indigo/atproto/syntax" 15 17 lexutil "github.com/bluesky-social/indigo/lex/util" 16 18 "github.com/go-chi/chi/v5" 17 19 ··· 24 26 "tangled.sh/tangled.sh/core/appview/pages/markup" 25 27 "tangled.sh/tangled.sh/core/appview/pagination" 26 28 "tangled.sh/tangled.sh/core/appview/reporesolver" 29 + "tangled.sh/tangled.sh/core/appview/validator" 30 + "tangled.sh/tangled.sh/core/appview/xrpcclient" 27 31 "tangled.sh/tangled.sh/core/idresolver" 32 + tlog "tangled.sh/tangled.sh/core/log" 28 33 "tangled.sh/tangled.sh/core/tid" 29 34 ) 30 35 ··· 36 41 db *db.DB 37 42 config *config.Config 38 43 notifier notify.Notifier 44 + logger *slog.Logger 45 + validator *validator.Validator 39 46 } 40 47 41 48 func New( ··· 55 62 db: db, 56 63 config: config, 57 64 notifier: notifier, 65 + logger: tlog.New("issues"), 66 + validator: validator, 58 67 } 59 68 } 60 69 61 70 func (rp *Issues) RepoSingleIssue(w http.ResponseWriter, r *http.Request) { 71 + l := rp.logger.With("handler", "RepoSingleIssue") 62 72 user := rp.oauth.GetUser(r) 63 73 f, err := rp.repoResolver.Resolve(r) 64 74 if err != nil { ··· 66 76 return 67 77 } 68 78 69 - issueId := chi.URLParam(r, "issue") 70 - issueIdInt, err := strconv.Atoi(issueId) 71 - if err != nil { 72 - http.Error(w, "bad issue id", http.StatusBadRequest) 73 - log.Println("failed to parse issue id", err) 74 - return 75 - } 76 - 77 - issue, comments, err := db.GetIssueWithComments(rp.db, f.RepoAt(), issueIdInt) 78 - if err != nil { 79 - log.Println("failed to get issue and comments", err) 80 - rp.pages.Notice(w, "issues", "Failed to load issue. Try again later.") 79 + issue, ok := r.Context().Value("issue").(*db.Issue) 80 + if !ok { 81 + l.Error("failed to get issue") 82 + rp.pages.Error404(w) 81 83 return 82 84 } 83 85 84 86 reactionCountMap, err := db.GetReactionCountMap(rp.db, issue.AtUri()) 85 87 if err != nil { 86 - log.Println("failed to get issue reactions") 87 - rp.pages.Notice(w, "issues", "Failed to load issue. Try again later.") 88 + l.Error("failed to get issue reactions", "err", err) 88 89 } 89 90 90 91 userReactions := map[db.ReactionKind]bool{} ··· 92 93 userReactions = db.GetReactionStatusMap(rp.db, user.Did, issue.AtUri()) 93 94 } 94 95 95 - issueOwnerIdent, err := rp.idResolver.ResolveIdent(r.Context(), issue.OwnerDid) 96 - if err != nil { 97 - log.Println("failed to resolve issue owner", err) 98 - } 99 - 100 96 rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ 101 - LoggedInUser: user, 102 - RepoInfo: f.RepoInfo(user), 103 - Issue: issue, 104 - Comments: comments, 105 - 106 - IssueOwnerHandle: issueOwnerIdent.Handle.String(), 107 - 97 + LoggedInUser: user, 98 + RepoInfo: f.RepoInfo(user), 99 + Issue: issue, 100 + CommentList: issue.CommentList(), 108 101 OrderedReactionKinds: db.OrderedReactionKinds, 109 102 Reactions: reactionCountMap, 110 103 UserReacted: userReactions, ··· 113 106 } 114 107 115 108 func (rp *Issues) CloseIssue(w http.ResponseWriter, r *http.Request) { 109 + l := rp.logger.With("handler", "CloseIssue") 116 110 user := rp.oauth.GetUser(r) 117 111 f, err := rp.repoResolver.Resolve(r) 118 112 if err != nil { 119 - log.Println("failed to get repo and knot", err) 120 - return 121 - } 122 - 123 - issueId := chi.URLParam(r, "issue") 124 - issueIdInt, err := strconv.Atoi(issueId) 125 - if err != nil { 126 - http.Error(w, "bad issue id", http.StatusBadRequest) 127 - log.Println("failed to parse issue id", err) 113 + l.Error("failed to get repo and knot", "err", err) 128 114 return 129 115 } 130 116 131 - issue, err := db.GetIssue(rp.db, f.RepoAt(), issueIdInt) 132 - if err != nil { 133 - log.Println("failed to get issue", err) 134 - rp.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.") 117 + issue, ok := r.Context().Value("issue").(*db.Issue) 118 + if !ok { 119 + l.Error("failed to get issue") 120 + rp.pages.Error404(w) 135 121 return 136 122 } 137 123 ··· 142 128 isCollaborator := slices.ContainsFunc(collaborators, func(collab pages.Collaborator) bool { 143 129 return user.Did == collab.Did 144 130 }) 145 - isIssueOwner := user.Did == issue.OwnerDid 131 + isIssueOwner := user.Did == issue.Did 146 132 147 133 // TODO: make this more granular 148 134 if isIssueOwner || isCollaborator { 149 - 150 - closed := tangled.RepoIssueStateClosed 151 - 152 - client, err := rp.oauth.AuthorizedClient(r) 153 - if err != nil { 154 - log.Println("failed to get authorized client", err) 155 - return 156 - } 157 - _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ 158 - Collection: tangled.RepoIssueStateNSID, 159 - Repo: user.Did, 160 - Rkey: tid.TID(), 161 - Record: &lexutil.LexiconTypeDecoder{ 162 - Val: &tangled.RepoIssueState{ 163 - Issue: issue.AtUri().String(), 164 - State: closed, 165 - }, 166 - }, 167 - }) 168 - 169 - if err != nil { 170 - log.Println("failed to update issue state", err) 171 - rp.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.") 172 - return 173 - } 174 - 175 - err = db.CloseIssue(rp.db, f.RepoAt(), issueIdInt) 135 + err = db.CloseIssues( 136 + rp.db, 137 + db.FilterEq("id", issue.Id), 138 + ) 176 139 if err != nil { 177 140 log.Println("failed to close issue", err) 178 141 rp.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.") 179 142 return 180 143 } 181 144 182 - rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueIdInt)) 145 + rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issue.IssueId)) 183 146 return 184 147 } else { 185 148 log.Println("user is not permitted to close issue") ··· 189 152 } 190 153 191 154 func (rp *Issues) ReopenIssue(w http.ResponseWriter, r *http.Request) { 155 + l := rp.logger.With("handler", "ReopenIssue") 192 156 user := rp.oauth.GetUser(r) 193 157 f, err := rp.repoResolver.Resolve(r) 194 158 if err != nil { ··· 196 160 return 197 161 } 198 162 199 - issueId := chi.URLParam(r, "issue") 200 - issueIdInt, err := strconv.Atoi(issueId) 201 - if err != nil { 202 - http.Error(w, "bad issue id", http.StatusBadRequest) 203 - log.Println("failed to parse issue id", err) 204 - return 205 - } 206 - 207 - issue, err := db.GetIssue(rp.db, f.RepoAt(), issueIdInt) 208 - if err != nil { 209 - log.Println("failed to get issue", err) 210 - rp.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.") 163 + issue, ok := r.Context().Value("issue").(*db.Issue) 164 + if !ok { 165 + l.Error("failed to get issue") 166 + rp.pages.Error404(w) 211 167 return 212 168 } 213 169 ··· 218 174 isCollaborator := slices.ContainsFunc(collaborators, func(collab pages.Collaborator) bool { 219 175 return user.Did == collab.Did 220 176 }) 221 - isIssueOwner := user.Did == issue.OwnerDid 177 + isIssueOwner := user.Did == issue.Did 222 178 223 179 if isCollaborator || isIssueOwner { 224 - err := db.ReopenIssue(rp.db, f.RepoAt(), issueIdInt) 180 + err := db.ReopenIssues( 181 + rp.db, 182 + db.FilterEq("id", issue.Id), 183 + ) 225 184 if err != nil { 226 185 log.Println("failed to reopen issue", err) 227 186 rp.pages.Notice(w, "issue-action", "Failed to reopen issue. Try again later.") 228 187 return 229 188 } 230 - rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueIdInt)) 189 + rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issue.IssueId)) 231 190 return 232 191 } else { 233 192 log.Println("user is not the owner of the repo") ··· 237 196 } 238 197 239 198 func (rp *Issues) NewIssueComment(w http.ResponseWriter, r *http.Request) { 199 + l := rp.logger.With("handler", "NewIssueComment") 240 200 user := rp.oauth.GetUser(r) 241 201 f, err := rp.repoResolver.Resolve(r) 242 202 if err != nil { ··· 411 371 LoggedInUser: user, 412 372 RepoInfo: f.RepoInfo(user), 413 373 Issue: issue, 414 - Comment: comment, 374 + Comment: &comment, 415 375 }) 416 376 case http.MethodPost: 417 377 // extract form value