Files
2026-06-15 23:48:37 +08:00

218 lines
6.1 KiB
Go

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,
})
}