package web // Functions used for user profiles in the web UI import ( "fmt" "net/http" "os" "strconv" "time" "muzi/db" "muzi/scrobble" "github.com/go-chi/chi/v5" "github.com/jackc/pgtype" ) type ProfileData struct { Username string Bio string Pfp string AllowDuplicateEdits bool ScrobbleCount int ArtistCount int Artists []string Titles []string Times []time.Time Page int Title string LoggedInUsername string TemplateName string NowPlayingArtist string NowPlayingTitle string } // Render a page of the profile in the URL func profilePageHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { username := chi.URLParam(r, "username") userId, err := getUserIdByUsername(r.Context(), username) if err != nil { fmt.Fprintf(os.Stderr, "Cannot find user %s: %v\n", username, err) http.Error(w, "User not found", http.StatusNotFound) return } pageStr := r.URL.Query().Get("page") var pageInt int if pageStr == "" { pageInt = 1 } else { pageInt, err = strconv.Atoi(pageStr) if err != nil { fmt.Fprintf(os.Stderr, "Cannot convert page URL query from string to int: %v\n", err) pageInt = 1 } } lim := 15 off := (pageInt - 1) * lim var profileData ProfileData profileData.Username = username profileData.Page = pageInt profileData.Title = username + "'s Profile" profileData.LoggedInUsername = getLoggedInUsername(r) profileData.TemplateName = "profile" err = db.Pool.QueryRow( r.Context(), `SELECT bio, pfp, allow_duplicate_edits, (SELECT COUNT(*) FROM history WHERE user_id = $1) as scrobble_count, (SELECT COUNT(DISTINCT artist) FROM history WHERE user_id = $1) as artist_count FROM users WHERE pk = $1;`, userId, ).Scan(&profileData.Bio, &profileData.Pfp, &profileData.AllowDuplicateEdits, &profileData.ScrobbleCount, &profileData.ArtistCount) if err != nil { fmt.Fprintf(os.Stderr, "Cannot get profile for %s: %v\n", username, err) http.Error(w, err.Error(), http.StatusInternalServerError) return } if np, ok := scrobble.GetNowPlaying(userId); ok { profileData.NowPlayingArtist = np.Artist profileData.NowPlayingTitle = np.SongName } rows, err := db.Pool.Query( r.Context(), "SELECT artist, song_name, timestamp FROM history WHERE user_id = $1 ORDER BY timestamp DESC LIMIT $2 OFFSET $3;", userId, lim, off, ) if err != nil { fmt.Fprintf(os.Stderr, "SELECT history failed: %v\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } defer rows.Close() for rows.Next() { var artist, title string var time pgtype.Timestamptz err = rows.Scan(&artist, &title, &time) if err != nil { fmt.Fprintf(os.Stderr, "Scanning history row failed: %v\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } profileData.Artists = append(profileData.Artists, artist) profileData.Titles = append(profileData.Titles, title) profileData.Times = append(profileData.Times, time.Time) } err = templates.ExecuteTemplate(w, "base", profileData) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } }