Monorepo for Tangled
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

more ui noodling

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

+201 -95
+153 -71
appview/db/vouch.go
··· 64 64 defer rows.Close() 65 65 66 66 for rows.Next() { 67 - var vouch models.Vouch 68 - var createdAt string 69 - var cidStr string 70 - var reason sql.NullString 71 - err := rows.Scan( 72 - &vouch.Did, 73 - &vouch.SubjectDid, 74 - &cidStr, 75 - &vouch.Kind, 76 - &reason, 77 - &createdAt, 78 - ) 67 + vouch, err := scanVouch(rows) 79 68 if err != nil { 80 - return nil, err 81 - } 82 - 83 - vouch.Cid, err = cid.Parse(cidStr) 84 - if err != nil { 85 - log.Println("unable to parse CID:", err) 69 + log.Println("error scanning vouch:", err) 86 70 continue 87 - } 88 - 89 - createdAtTime, err := time.Parse(time.RFC3339, createdAt) 90 - if err != nil { 91 - log.Println("unable to determine created at time") 92 - vouch.CreatedAt = time.Now() 93 - } else { 94 - vouch.CreatedAt = createdAtTime 95 - } 96 - if reason.Valid { 97 - vouch.Reason = &reason.String 98 71 } 99 72 vouches = append(vouches, vouch) 100 73 } ··· 199 172 200 173 var vouches []models.Vouch 201 174 for rows.Next() { 202 - var vouch models.Vouch 203 - var createdAt string 204 - var cidStr string 205 - var reason sql.NullString 206 - err := rows.Scan( 207 - &vouch.Did, 208 - &vouch.SubjectDid, 209 - &cidStr, 210 - &vouch.Kind, 211 - &reason, 212 - &createdAt, 213 - ) 175 + vouch, err := scanVouch(rows) 214 176 if err != nil { 215 - return nil, err 216 - } 217 - 218 - vouch.Cid, err = cid.Parse(cidStr) 219 - if err != nil { 220 - log.Println("unable to parse CID:", err) 177 + log.Println("error scanning vouch:", err) 221 178 continue 222 179 } 223 - 224 - createdAtTime, err := time.Parse(time.RFC3339, createdAt) 225 - if err != nil { 226 - log.Println("unable to determine created at time") 227 - vouch.CreatedAt = time.Now() 228 - } else { 229 - vouch.CreatedAt = createdAtTime 230 - } 231 - if reason.Valid { 232 - vouch.Reason = &reason.String 233 - } 234 180 vouches = append(vouches, vouch) 235 181 } 236 182 return vouches, nil 237 183 } 238 184 239 - func GetVouchRelationship(e Execer, viewerDid, subjectDid string) (*models.VouchRelationship, error) { 240 - relationship := &models.VouchRelationship{ 241 - ViewerDid: viewerDid, 242 - SubjectDid: subjectDid, 243 - NetworkVouches: []models.Vouch{}, 185 + // scanVouch scans a single vouch row from the database 186 + func scanVouch(rows *sql.Rows) (models.Vouch, error) { 187 + var vouch models.Vouch 188 + var createdAt string 189 + var cidStr string 190 + var reason sql.NullString 191 + err := rows.Scan( 192 + &vouch.Did, 193 + &vouch.SubjectDid, 194 + &cidStr, 195 + &vouch.Kind, 196 + &reason, 197 + &createdAt, 198 + ) 199 + if err != nil { 200 + return models.Vouch{}, err 244 201 } 245 202 246 - // Get direct vouch from viewer to subject (if exists) 247 - directVouch, err := GetVouch(e, viewerDid, subjectDid) 248 - if err == nil { 249 - relationship.NetworkVouches = append(relationship.NetworkVouches, *directVouch) 250 - } else if err != sql.ErrNoRows { 203 + vouch.Cid, err = cid.Parse(cidStr) 204 + if err != nil { 205 + log.Println("unable to parse CID:", err) 206 + return models.Vouch{}, err 207 + } 208 + 209 + createdAtTime, err := time.Parse(time.RFC3339, createdAt) 210 + if err != nil { 211 + log.Println("unable to determine created at time") 212 + vouch.CreatedAt = time.Now() 213 + } else { 214 + vouch.CreatedAt = createdAtTime 215 + } 216 + if reason.Valid { 217 + vouch.Reason = &reason.String 218 + } 219 + return vouch, nil 220 + } 221 + 222 + // GetVouchRelationshipsBatch retrieves vouch relationships for multiple subjects from the viewer's perspective 223 + func GetVouchRelationshipsBatch(e Execer, viewerDid string, subjectDids []string) (map[string]*models.VouchRelationship, error) { 224 + if viewerDid == "" { 225 + return nil, fmt.Errorf("viewerDid cannot be empty") 226 + } 227 + 228 + // Initialize result map with empty relationships for all subjects 229 + result := make(map[string]*models.VouchRelationship) 230 + for _, subjectDid := range subjectDids { 231 + result[subjectDid] = &models.VouchRelationship{ 232 + ViewerDid: viewerDid, 233 + SubjectDid: subjectDid, 234 + NetworkVouches: []models.Vouch{}, 235 + } 236 + } 237 + 238 + if len(subjectDids) == 0 { 239 + return result, nil 240 + } 241 + 242 + // Build placeholders for IN clause 243 + placeholders := make([]string, len(subjectDids)) 244 + args := make([]any, len(subjectDids)+1) 245 + args[0] = viewerDid 246 + for i, did := range subjectDids { 247 + placeholders[i] = "?" 248 + args[i+1] = did 249 + } 250 + placeholderStr := strings.Join(placeholders, ",") 251 + 252 + // Query 1: Get direct vouches from viewer to all subjects 253 + directQuery := fmt.Sprintf(` 254 + SELECT did, subject_did, cid, kind, reason, created_at 255 + FROM vouches 256 + WHERE did = ? AND subject_did IN (%s) 257 + `, placeholderStr) 258 + 259 + rows, err := e.Query(directQuery, args...) 260 + if err != nil { 251 261 return nil, err 252 262 } 263 + defer rows.Close() 253 264 254 - networkVouches, err := GetNetworkVouchesForSubject(e, viewerDid, subjectDid, 0) 265 + for rows.Next() { 266 + vouch, err := scanVouch(rows) 267 + if err != nil { 268 + return nil, err 269 + } 270 + if rel, ok := result[vouch.SubjectDid]; ok { 271 + rel.NetworkVouches = append(rel.NetworkVouches, vouch) 272 + } 273 + } 274 + 275 + // Query 2a: Get viewer's network (people viewer has vouched for) 276 + networkQuery := ` 277 + SELECT subject_did 278 + FROM vouches 279 + WHERE did = ? AND kind = 'vouch' 280 + ` 281 + rows, err = e.Query(networkQuery, viewerDid) 255 282 if err != nil { 256 283 return nil, err 257 284 } 285 + defer rows.Close() 258 286 259 - relationship.NetworkVouches = append(relationship.NetworkVouches, networkVouches...) 287 + var network []string 288 + for rows.Next() { 289 + var did string 290 + if err := rows.Scan(&did); err != nil { 291 + return nil, err 292 + } 293 + network = append(network, did) 294 + } 260 295 261 - return relationship, nil 296 + // Query 2b: If viewer has a network, get vouches from network to subjects 297 + if len(network) > 0 { 298 + // Build placeholders for network DIDs 299 + networkPlaceholders := make([]string, len(network)) 300 + networkArgs := make([]any, 0, len(subjectDids)+len(network)) 301 + 302 + // First add subject DIDs 303 + for _, did := range subjectDids { 304 + networkArgs = append(networkArgs, did) 305 + } 306 + // Then add network DIDs 307 + for i, did := range network { 308 + networkPlaceholders[i] = "?" 309 + networkArgs = append(networkArgs, did) 310 + } 311 + 312 + networkVouchQuery := fmt.Sprintf(` 313 + SELECT did, subject_did, cid, kind, reason, created_at 314 + FROM vouches 315 + WHERE subject_did IN (%s) AND did IN (%s) 316 + `, placeholderStr, strings.Join(networkPlaceholders, ",")) 317 + 318 + rows, err = e.Query(networkVouchQuery, networkArgs...) 319 + if err != nil { 320 + return nil, err 321 + } 322 + defer rows.Close() 323 + 324 + for rows.Next() { 325 + vouch, err := scanVouch(rows) 326 + if err != nil { 327 + return nil, err 328 + } 329 + if rel, ok := result[vouch.SubjectDid]; ok { 330 + rel.NetworkVouches = append(rel.NetworkVouches, vouch) 331 + } 332 + } 333 + } 334 + 335 + return result, nil 336 + } 337 + 338 + func GetVouchRelationship(e Execer, viewerDid, subjectDid string) (*models.VouchRelationship, error) { 339 + batch, err := GetVouchRelationshipsBatch(e, viewerDid, []string{subjectDid}) 340 + if err != nil { 341 + return nil, err 342 + } 343 + return batch[subjectDid], nil 262 344 }
+15 -10
appview/issues/issues.go
··· 127 127 return 128 128 } 129 129 130 - var vouchRelationship *models.VouchRelationship 130 + vouchRelationships := make(map[string]*models.VouchRelationship) 131 131 if user != nil { 132 - vouchRelationship, err := db.GetVouchRelationship(rp.db, user.Did, issue.Did) 132 + participants := issue.Participants() 133 + vouchRelationships, err = db.GetVouchRelationshipsBatch(rp.db, user.Did, participants) 134 + if err != nil { 135 + l.Error("failed to fetch vouch relationships", "err", err) 136 + } 133 137 } 134 138 135 139 defs := make(map[string]*models.LabelDefinition) ··· 138 142 } 139 143 140 144 rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ 141 - LoggedInUser: user, 142 - RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 143 - Issue: issue, 144 - CommentList: issue.CommentList(), 145 - Backlinks: backlinks, 146 - Reactions: reactionMap, 147 - UserReacted: userReactions, 148 - LabelDefs: defs, 145 + LoggedInUser: user, 146 + RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 147 + Issue: issue, 148 + CommentList: issue.CommentList(), 149 + Backlinks: backlinks, 150 + Reactions: reactionMap, 151 + UserReacted: userReactions, 152 + LabelDefs: defs, 153 + VouchRelationships: vouchRelationships, 149 154 }) 150 155 } 151 156
+3 -2
appview/pages/pages.go
··· 1131 1131 Backlinks []models.RichReferenceLink 1132 1132 LabelDefs map[string]*models.LabelDefinition 1133 1133 1134 - Reactions map[models.ReactionKind]models.ReactionDisplayData 1135 - UserReacted map[models.ReactionKind]bool 1134 + Reactions map[models.ReactionKind]models.ReactionDisplayData 1135 + UserReacted map[models.ReactionKind]bool 1136 + VouchRelationships map[string]*models.VouchRelationship 1136 1137 } 1137 1138 1138 1139 func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error {
+7
appview/pages/templates/fragments/icons/shield-alert.html
··· 1 + {{ define "fragments/icons/shield-alert" }} 2 + <svg class="{{.}}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3 + <path d="M20 13C20 18 16.5 20.5 12.34 21.95C12.1222 22.0238 11.8855 22.0202 11.67 21.94C7.5 20.5 4 18 4 13V5.99996C4 5.73474 4.10536 5.48039 4.29289 5.29285C4.48043 5.10532 4.73478 4.99996 5 4.99996C7 4.99996 9.5 3.79996 11.24 2.27996C11.4519 2.09896 11.7214 1.99951 12 1.99951C12.2786 1.99951 12.5481 2.09896 12.76 2.27996C14.51 3.80996 17 4.99996 19 4.99996C19.2652 4.99996 19.5196 5.10532 19.7071 5.29285C19.8946 5.48039 20 5.73474 20 5.99996V13Z" fill="#EF4444" stroke="#DC2626" stroke-linecap="round" stroke-linejoin="round"/> 4 + <path d="M12 8V12" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 5 + <path d="M12 16H12.01" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 6 + </svg> 7 + {{ end }}
+6
appview/pages/templates/fragments/icons/shield-check.html
··· 1 + {{ define "fragments/icons/shield-check" }} 2 + <svg class="{{.}}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3 + <path d="M20 13C20 18 16.5 20.5 12.34 21.95C12.1222 22.0238 11.8855 22.0202 11.67 21.94C7.5 20.5 4 18 4 13V5.99996C4 5.73474 4.10536 5.48039 4.29289 5.29285C4.48043 5.10532 4.73478 4.99996 5 4.99996C7 4.99996 9.5 3.79996 11.24 2.27996C11.4519 2.09896 11.7214 1.99951 12 1.99951C12.2786 1.99951 12.5481 2.09896 12.76 2.27996C14.51 3.80996 17 4.99996 19 4.99996C19.2652 4.99996 19.5196 5.10532 19.7071 5.29285C19.8946 5.48039 20 5.73474 20 5.99996V13Z" fill="#22C55E" stroke="#16A34A" stroke-linecap="round" stroke-linejoin="round"/> 4 + <path d="M9 12L11 14L15 10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 5 + </svg> 6 + {{ end }}
+8 -5
appview/pages/templates/repo/issues/fragments/commentList.html
··· 14 14 "RepoInfo" $root.RepoInfo 15 15 "LoggedInUser" $root.LoggedInUser 16 16 "Issue" $root.Issue 17 - "Comment" $comment.Self) }} 17 + "Comment" $comment.Self 18 + "VouchRelationship" (index $root.VouchRelationships $comment.Self.Did) 19 + ) }} 18 20 19 21 <div class="rounded border border-gray-200 dark:border-gray-700 w-full overflow-hidden shadow-sm bg-gray-50 dark:bg-gray-800/50"> 20 22 {{ template "topLevelComment" $params }} ··· 28 30 "RepoInfo" $root.RepoInfo 29 31 "LoggedInUser" $root.LoggedInUser 30 32 "Issue" $root.Issue 31 - "Comment" $reply) 32 - }} 33 + "Comment" $reply 34 + "VouchRelationship" (index $root.VouchRelationships $reply.Did) 35 + ) }} 33 36 </div> 34 37 {{ end }} 35 38 </div> ··· 41 44 {{ define "topLevelComment" }} 42 45 <div class="rounded px-6 py-4 bg-white dark:bg-gray-800 flex gap-2 "> 43 46 <div class="flex-shrink-0"> 44 - {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1") }} 47 + {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1" .VouchRelationship) }} 45 48 </div> 46 49 <div class="flex-1 min-w-0"> 47 50 {{ template "repo/issues/fragments/issueCommentHeader" . }} ··· 53 56 {{ define "replyComment" }} 54 57 <div class="py-4 pr-4 w-full mx-auto overflow-hidden flex gap-2 "> 55 58 <div class="flex-shrink-0"> 56 - {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1") }} 59 + {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1" .VouchRelationship) }} 57 60 </div> 58 61 <div class="flex-1 min-w-0"> 59 62 {{ template "repo/issues/fragments/issueCommentHeader" . }}
+3 -1
appview/pages/templates/repo/issues/issue.html
··· 118 118 "RepoInfo" $.RepoInfo 119 119 "LoggedInUser" $.LoggedInUser 120 120 "Issue" $.Issue 121 - "CommentList" $.Issue.CommentList) 121 + "CommentList" $.Issue.CommentList 122 + "VouchRelationships" $.VouchRelationships 123 + ) 122 124 }} 123 125 124 126 {{ template "repo/issues/fragments/newComment" . }}
+5 -5
appview/pages/templates/user/fragments/picLink.html
··· 1 1 {{ define "user/fragments/picLink" }} 2 2 {{ $did := index . 0 }} 3 3 {{ $classes := index . 1 }} 4 - {{ $vouchRelation := index . 2 }} 5 4 {{ $handle := resolve $did }} 6 5 {{ $isVouched := false }} 7 6 {{ $isDenounced := false }} 8 - {{ if $vouchRelation }} 7 + {{ if ge (len .) 3 }} 8 + {{ $vouchRelation := index . 2 }} 9 9 {{ if $vouchRelation.IsDirectVouch }} 10 10 {{ $isVouched = true }} 11 11 {{ else if $vouchRelation.IsDirectDenounce }} ··· 16 16 <img 17 17 src="{{ tinyAvatar $did }}" 18 18 alt="" 19 - class="rounded-full {{ if $isVouched }}border-2 border-green-500{{ else if $isDenounced }}border-2 border-red-500{{ else }}border border-gray-300 dark:border-gray-700{{ end }} {{ $classes }}" 19 + class="rounded-full border border-gray-300 dark:border-gray-700 {{ $classes }}" 20 20 /> 21 21 {{ if $isVouched }} 22 - <span class="absolute bottom-0 right-0">{{ i "shield" "size-3 text-green-500" }}</span> 22 + <span class="absolute bottom-0 right-0">{{ template "fragments/icons/shield-check" "size-4" }}</span> 23 23 {{ else if $isDenounced }} 24 - <span class="absolute bottom-0 right-0">{{ i "shield" "size-3 text-red-500" }}</span> 24 + <span class="absolute bottom-0 right-0">{{ template "fragments/icons/shield-alert" "size-4" }}</span> 25 25 {{ end }} 26 26 </a> 27 27 {{ end }}
+1 -1
appview/pages/templates/user/fragments/vouch.html
··· 17 17 {{ $userIdent := "" }} 18 18 {{ $userDid := "" }} 19 19 {{ with .VouchRelationship }} 20 - {{ $userIdent = .SubjectDid }} 20 + {{ $userDid = .SubjectDid }} 21 21 {{ $userIdent = resolve .SubjectDid }} 22 22 {{ end }} 23 23 <div class="relative">