back interdiff of round #2 and #1

appview/{pages,db}: show star/unstar buttons on the timeline #554

merged
opened by anirudh.fi targeting master from push-qrltzqmlrlln
files
appview
db
pages
templates
timeline
fragments
user
fragments
REBASED
appview/db/timeline.go

This patch was likely rebased, as context lines do not match.

REVERTED
appview/pages/templates/timeline/fragments/timeline.html
···
{{ with $e }}
<div class="flex flex-col divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-sm">
{{ if .Repo }}
-
{{ template "timeline/fragments/repoEvent" (list $ .) }}
{{ else if .Star }}
{{ template "timeline/fragments/starEvent" (list $ .Star) }}
{{ else if .Follow }}
···
{{ define "timeline/fragments/repoEvent" }}
{{ $root := index . 0 }}
-
{{ $event := index . 1 }}
-
{{ $repo := $event.Repo }}
-
{{ $source := $event.Source }}
{{ $userHandle := resolve $repo.Did }}
<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">
{{ template "user/fragments/picHandleLink" $repo.Did }}
···
<span class="text-gray-700 dark:text-gray-400 text-xs">{{ template "repo/fragments/time" $repo.Created }}</span>
</div>
{{ with $repo }}
-
{{ template "user/fragments/repoCard" (list $root . true true (dict "IsStarred" $event.IsStarred "RepoAt" .RepoAt "Stats" (dict "StarCount" $event.StarCount))) }}
{{ end }}
{{ end }}
···
{{ with $e }}
<div class="flex flex-col divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-sm">
{{ if .Repo }}
+
{{ template "timeline/fragments/repoEvent" (list $ .Repo .Source) }}
{{ else if .Star }}
{{ template "timeline/fragments/starEvent" (list $ .Star) }}
{{ else if .Follow }}
···
{{ define "timeline/fragments/repoEvent" }}
{{ $root := index . 0 }}
+
{{ $repo := index . 1 }}
+
{{ $source := index . 2 }}
{{ $userHandle := resolve $repo.Did }}
<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">
{{ template "user/fragments/picHandleLink" $repo.Did }}
···
<span class="text-gray-700 dark:text-gray-400 text-xs">{{ template "repo/fragments/time" $repo.Created }}</span>
</div>
{{ with $repo }}
+
{{ template "user/fragments/repoCard" (list $root . true) }}
{{ end }}
{{ end }}
REVERTED
appview/pages/templates/user/fragments/repoCard.html
···
{{ $root := index . 0 }}
{{ $repo := index . 1 }}
{{ $fullName := index . 2 }}
-
{{ $starButton := false }}
-
{{ $starData := dict }}
-
{{ if gt (len .) 3 }}
-
{{ $starButton = index . 3 }}
-
{{ if gt (len .) 4 }}
-
{{ $starData = index . 4 }}
-
{{ end }}
-
{{ end }}
{{ with $repo }}
<div class="py-4 px-6 gap-1 flex flex-col drop-shadow-sm rounded bg-white dark:bg-gray-800 min-h-32">
-
<div class="font-medium dark:text-white flex items-center justify-between">
-
<div class="flex items-center">
{{ if .Source }}
{{ i "git-fork" "w-4 h-4 mr-1.5 shrink-0" }}
{{ else }}
{{ i "book-marked" "w-4 h-4 mr-1.5 shrink-0" }}
{{ end }}
-
{{ $repoOwner := resolve .Did }}
-
{{- if $fullName -}}
-
<a href="/{{ $repoOwner }}/{{ .Name }}" class="truncate">{{ $repoOwner }}/{{ .Name }}</a>
-
{{- else -}}
-
<a href="/{{ $repoOwner }}/{{ .Name }}" class="truncate">{{ .Name }}</a>
-
{{- end -}}
-
</div>
-
-
{{ if and $starButton $root.LoggedInUser }}
-
{{ template "repo/fragments/repoStar" $starData }}
-
{{ end }}
</div>
{{ with .Description }}
<div class="text-gray-600 dark:text-gray-300 text-sm line-clamp-2">
···
{{ $root := index . 0 }}
{{ $repo := index . 1 }}
{{ $fullName := index . 2 }}
{{ with $repo }}
<div class="py-4 px-6 gap-1 flex flex-col drop-shadow-sm rounded bg-white dark:bg-gray-800 min-h-32">
+
<div class="font-medium dark:text-white flex items-center">
{{ if .Source }}
{{ i "git-fork" "w-4 h-4 mr-1.5 shrink-0" }}
{{ else }}
{{ i "book-marked" "w-4 h-4 mr-1.5 shrink-0" }}
{{ end }}
+
{{ $repoOwner := resolve .Did }}
+
{{- if $fullName -}}
+
<a href="/{{ $repoOwner }}/{{ .Name }}" class="truncate">{{ $repoOwner }}/{{ .Name }}</a>
+
{{- else -}}
+
<a href="/{{ $repoOwner }}/{{ .Name }}" class="truncate">{{ .Name }}</a>
+
{{- end -}}
</div>
{{ with .Description }}
<div class="text-gray-600 dark:text-gray-300 text-sm line-clamp-2">
NEW
appview/db/follow.go
···
}
}
func GetFollowStatus(e Execer, userDid, subjectDid string) FollowStatus {
-
if userDid == subjectDid {
-
return IsSelf
-
} else if _, err := GetFollow(e, userDid, subjectDid); err != nil {
return IsNotFollowing
-
} else {
-
return IsFollowing
}
}
···
}
}
+
func getFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]FollowStatus, error) {
+
if len(subjectDids) == 0 || userDid == "" {
+
return make(map[string]FollowStatus), nil
+
}
+
+
result := make(map[string]FollowStatus)
+
+
for _, subjectDid := range subjectDids {
+
if userDid == subjectDid {
+
result[subjectDid] = IsSelf
+
} else {
+
result[subjectDid] = IsNotFollowing
+
}
+
}
+
+
var querySubjects []string
+
for _, subjectDid := range subjectDids {
+
if userDid != subjectDid {
+
querySubjects = append(querySubjects, subjectDid)
+
}
+
}
+
+
if len(querySubjects) == 0 {
+
return result, nil
+
}
+
+
placeholders := make([]string, len(querySubjects))
+
args := make([]any, len(querySubjects)+1)
+
args[0] = userDid
+
+
for i, subjectDid := range querySubjects {
+
placeholders[i] = "?"
+
args[i+1] = subjectDid
+
}
+
+
query := fmt.Sprintf(`
+
SELECT subject_did
+
FROM follows
+
WHERE user_did = ? AND subject_did IN (%s)
+
`, strings.Join(placeholders, ","))
+
+
rows, err := e.Query(query, args...)
+
if err != nil {
+
return nil, err
+
}
+
defer rows.Close()
+
+
for rows.Next() {
+
var subjectDid string
+
if err := rows.Scan(&subjectDid); err != nil {
+
return nil, err
+
}
+
result[subjectDid] = IsFollowing
+
}
+
+
return result, nil
+
}
+
func GetFollowStatus(e Execer, userDid, subjectDid string) FollowStatus {
+
statuses, err := getFollowStatuses(e, userDid, []string{subjectDid})
+
if err != nil {
return IsNotFollowing
}
+
return statuses[subjectDid]
+
}
+
+
func GetFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]FollowStatus, error) {
+
return getFollowStatuses(e, userDid, subjectDids)
}
NEW
appview/db/star.go
···
return stars, nil
}
func GetStarStatus(e Execer, userDid string, repoAt syntax.ATURI) bool {
-
if _, err := GetStar(e, userDid, repoAt); err != nil {
return false
-
} else {
-
return true
}
}
func GetStars(e Execer, limit int, filters ...filter) ([]Star, error) {
var conditions []string
var args []any
···
return stars, nil
}
+
// getStarStatuses returns a map of repo URIs to star status for a given user
+
// This is an internal helper function to avoid N+1 queries
+
func getStarStatuses(e Execer, userDid string, repoAts []syntax.ATURI) (map[string]bool, error) {
+
if len(repoAts) == 0 || userDid == "" {
+
return make(map[string]bool), nil
+
}
+
+
placeholders := make([]string, len(repoAts))
+
args := make([]any, len(repoAts)+1)
+
args[0] = userDid
+
+
for i, repoAt := range repoAts {
+
placeholders[i] = "?"
+
args[i+1] = repoAt.String()
+
}
+
+
query := fmt.Sprintf(`
+
SELECT repo_at
+
FROM stars
+
WHERE starred_by_did = ? AND repo_at IN (%s)
+
`, strings.Join(placeholders, ","))
+
+
rows, err := e.Query(query, args...)
+
if err != nil {
+
return nil, err
+
}
+
defer rows.Close()
+
+
result := make(map[string]bool)
+
// Initialize all repos as not starred
+
for _, repoAt := range repoAts {
+
result[repoAt.String()] = false
+
}
+
+
// Mark starred repos as true
+
for rows.Next() {
+
var repoAt string
+
if err := rows.Scan(&repoAt); err != nil {
+
return nil, err
+
}
+
result[repoAt] = true
+
}
+
+
return result, nil
+
}
+
func GetStarStatus(e Execer, userDid string, repoAt syntax.ATURI) bool {
+
statuses, err := getStarStatuses(e, userDid, []syntax.ATURI{repoAt})
+
if err != nil {
return false
}
+
return statuses[repoAt.String()]
}
+
// GetStarStatuses returns a map of repo URIs to star status for a given user
+
func GetStarStatuses(e Execer, userDid string, repoAts []syntax.ATURI) (map[string]bool, error) {
+
return getStarStatuses(e, userDid, repoAts)
+
}
func GetStars(e Execer, limit int, filters ...filter) ([]Star, error) {
var conditions []string
var args []any