From a091f0ef0e2908c7ae5989163a5c1412de10de34 Mon Sep 17 00:00:00 2001 From: Dave Gallant Date: Sat, 14 Feb 2026 09:15:36 -0500 Subject: [PATCH] Add tooltip when hovering over deal --- backend/app.go | 85 +++++++++++++++++++++ backend/model.go | 6 ++ src/App.vue | 189 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 276 insertions(+), 4 deletions(-) diff --git a/backend/app.go b/backend/app.go index 10436da..81771b4 100644 --- a/backend/app.go +++ b/backend/app.go @@ -63,6 +63,7 @@ func (a *App) Run(httpPort string) { func (a *App) initializeRoutes() { a.Router.HandleFunc("/topics", a.listTopics).Methods("GET") + a.Router.HandleFunc("/topics/{id}", a.getTopicDetails).Methods("GET") } // func respondWithError(w http.ResponseWriter, code int, message string) { @@ -87,6 +88,90 @@ func (a *App) listTopics(w http.ResponseWriter, r *http.Request) { respondWithJSON(w, http.StatusOK, a.CurrentTopics) } +// getTopicDetails godoc +// @Summary Get detailed information about a specific topic +// @Description Fetches full details including recent comments for a topic by ID +// @ID get-topic-details +// @Param id path int true "Topic ID" +// @Router /topics/{id} [get] +// @Success 200 {object} TopicDetails +func (a *App) getTopicDetails(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + topicID := vars["id"] + + // Find topic in current topics + var topic *Topic + for i := range a.CurrentTopics { + if fmt.Sprintf("%d", a.CurrentTopics[i].TopicID) == topicID { + topic = &a.CurrentTopics[i] + break + } + } + + if topic == nil { + respondWithJSON(w, http.StatusNotFound, map[string]string{"error": "Topic not found"}) + return + } + + // Fetch detailed info from RFD API + requestURL := fmt.Sprintf("https://forums.redflagdeals.com/api/topics/%s", topicID) + res, err := http.Get(requestURL) + if err != nil { + log.Warn().Msgf("error fetching topic details: %s\n", err) + respondWithJSON(w, http.StatusInternalServerError, map[string]string{"error": "Failed to fetch details"}) + return + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Warn().Msgf("could not read response body: %s\n", err) + respondWithJSON(w, http.StatusInternalServerError, map[string]string{"error": "Failed to read response"}) + return + } + + var rfdResponse map[string]interface{} + err = json.Unmarshal([]byte(body), &rfdResponse) + if err != nil { + log.Warn().Msgf("could not unmarshal response body: %s", err) + respondWithJSON(w, http.StatusInternalServerError, map[string]string{"error": "Failed to parse response"}) + return + } + + // Extract relevant fields for tooltip + details := TopicDetails{ + Topic: *topic, + Description: extractDescription(rfdResponse), + FirstPost: extractFirstPost(rfdResponse), + } + + respondWithJSON(w, http.StatusOK, details) +} + +func extractDescription(data map[string]interface{}) string { + if topic, ok := data["topic"].(map[string]interface{}); ok { + if description, ok := topic["description"].(string); ok { + return description + } + } + return "" +} + +func extractFirstPost(data map[string]interface{}) string { + if posts, ok := data["posts"].([]interface{}); ok && len(posts) > 0 { + if firstPost, ok := posts[0].(map[string]interface{}); ok { + if body, ok := firstPost["body"].(string); ok { + // Truncate to first 200 characters + if len(body) > 200 { + return body[:200] + "..." + } + return body + } + } + } + return "" +} + func (a *App) refreshTopics() { for { log.Info().Msg("Refreshing topics") diff --git a/backend/model.go b/backend/model.go index 9353c26..16236fc 100644 --- a/backend/model.go +++ b/backend/model.go @@ -27,3 +27,9 @@ type Offer struct { DealerName string `json:"dealer_name"` Url string `json:"url"` } // @name Offer + +type TopicDetails struct { + Topic Topic `json:"topic"` + Description string `json:"description"` + FirstPost string `json:"first_post"` +} // @name TopicDetails diff --git a/src/App.vue b/src/App.vue index f53d481..9d9fcd3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,11 +1,15 @@