package handler import ( "encoding/json" "net/http" "strconv" "github.com/enterprise-ai-platform/server/internal/middleware" "github.com/enterprise-ai-platform/server/internal/response" "github.com/go-chi/chi/v5" "github.com/jackc/pgx/v5/pgxpool" ) type FavoriteHandler struct { pool *pgxpool.Pool } func NewFavoriteHandler(pool *pgxpool.Pool) *FavoriteHandler { return &FavoriteHandler{pool: pool} } func (h *FavoriteHandler) AddFavorite(w http.ResponseWriter, r *http.Request) { appID := chi.URLParam(r, "id") userID := middleware.GetUserID(r.Context()) _, err := h.pool.Exec(r.Context(), `INSERT INTO app_favorites (user_id, app_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`, userID, appID) if err != nil { response.InternalError(w, "收藏失败") return } h.pool.Exec(r.Context(), `UPDATE applications SET favorite_count = favorite_count + 1 WHERE id = $1`, appID) response.JSON(w, http.StatusOK, map[string]bool{"favorited": true}) } func (h *FavoriteHandler) RemoveFavorite(w http.ResponseWriter, r *http.Request) { appID := chi.URLParam(r, "id") userID := middleware.GetUserID(r.Context()) tag, err := h.pool.Exec(r.Context(), `DELETE FROM app_favorites WHERE user_id = $1 AND app_id = $2`, userID, appID) if err != nil { response.InternalError(w, "取消收藏失败") return } if tag.RowsAffected() > 0 { h.pool.Exec(r.Context(), `UPDATE applications SET favorite_count = GREATEST(favorite_count - 1, 0) WHERE id = $1`, appID) } response.JSON(w, http.StatusOK, map[string]bool{"favorited": false}) } func (h *FavoriteHandler) ListFavorites(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) page, _ := strconv.Atoi(r.URL.Query().Get("page")) if page < 1 { page = 1 } offset := (page - 1) * 20 rows, err := h.pool.Query(r.Context(), ` SELECT a.id, a.name, a.slug, a.description, a.icon_url, c.name as category_name, c.slug as category_slug, a.usage_count, a.avg_rating, a.rating_count FROM app_favorites f JOIN applications a ON f.app_id = a.id LEFT JOIN categories c ON a.category_id = c.id WHERE f.user_id = $1 ORDER BY f.created_at DESC LIMIT 20 OFFSET $2`, userID, offset) if err != nil { response.InternalError(w, "查询收藏失败") return } defer rows.Close() apps := scanAppList(rows) response.JSON(w, http.StatusOK, apps) } // --- Rating --- type ratingRequest struct { Score int `json:"score"` Comment string `json:"comment"` } func (h *FavoriteHandler) AddRating(w http.ResponseWriter, r *http.Request) { appID := chi.URLParam(r, "id") userID := middleware.GetUserID(r.Context()) var req ratingRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { response.BadRequest(w, "无效的请求格式") return } if req.Score < 1 || req.Score > 5 { response.BadRequest(w, "评分必须在1-5之间") return } _, err := h.pool.Exec(r.Context(), ` INSERT INTO app_ratings (app_id, user_id, score, comment) VALUES ($1, $2, $3, $4) ON CONFLICT (app_id, user_id) DO UPDATE SET score = EXCLUDED.score, comment = EXCLUDED.comment`, appID, userID, req.Score, req.Comment) if err != nil { response.InternalError(w, "评分失败") return } // Update app avg rating var avgRating float32 var ratingCount int h.pool.QueryRow(r.Context(), `SELECT COALESCE(AVG(score)::REAL, 0), COUNT(*) FROM app_ratings WHERE app_id = $1`, appID).Scan(&avgRating, &ratingCount) h.pool.Exec(r.Context(), `UPDATE applications SET avg_rating = $2, rating_count = $3 WHERE id = $1`, appID, avgRating, ratingCount) response.JSON(w, http.StatusOK, map[string]any{ "avg_rating": avgRating, "rating_count": ratingCount, }) } func (h *FavoriteHandler) ListRatings(w http.ResponseWriter, r *http.Request) { appID := chi.URLParam(r, "id") rows, err := h.pool.Query(r.Context(), ` SELECT r.id, r.score, r.comment, r.created_at, u.name as user_name, u.avatar_url FROM app_ratings r JOIN users u ON r.user_id = u.id WHERE r.app_id = $1 ORDER BY r.created_at DESC LIMIT 50`, appID) if err != nil { response.InternalError(w, "查询评分失败") return } defer rows.Close() var ratings []map[string]any for rows.Next() { var id string var score int var comment *string var createdAt string var userName string var avatarURL *string if err := rows.Scan(&id, &score, &comment, &createdAt, &userName, &avatarURL); err != nil { continue } ratings = append(ratings, map[string]any{ "id": id, "score": score, "comment": comment, "created_at": createdAt, "user_name": userName, "user_avatar": avatarURL, }) } if ratings == nil { ratings = []map[string]any{} } response.JSON(w, http.StatusOK, ratings) } func (h *FavoriteHandler) PersonalStats(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) var totalConversations, totalTokens, favoriteCount int h.pool.QueryRow(r.Context(), `SELECT COUNT(*) FROM app_usage_logs WHERE user_id = $1`, userID).Scan(&totalConversations) h.pool.QueryRow(r.Context(), `SELECT COALESCE(SUM(total_tokens), 0) FROM app_usage_logs WHERE user_id = $1`, userID).Scan(&totalTokens) h.pool.QueryRow(r.Context(), `SELECT COUNT(*) FROM app_favorites WHERE user_id = $1`, userID).Scan(&favoriteCount) var recentApps []map[string]any rows, err := h.pool.Query(r.Context(), ` SELECT DISTINCT ON (l.app_id) a.id, a.name, a.icon_url, l.created_at FROM app_usage_logs l JOIN applications a ON l.app_id = a.id WHERE l.user_id = $1 ORDER BY l.app_id, l.created_at DESC LIMIT 5`, userID) if err == nil { defer rows.Close() for rows.Next() { var id, name string var icon *string var at string if rows.Scan(&id, &name, &icon, &at) == nil { recentApps = append(recentApps, map[string]any{ "id": id, "name": name, "icon_url": icon, "last_used": at, }) } } } if recentApps == nil { recentApps = []map[string]any{} } response.JSON(w, http.StatusOK, map[string]any{ "total_conversations": totalConversations, "total_tokens": totalTokens, "favorite_count": favoriteCount, "recent_apps": recentApps, }) }