218 lines
6.1 KiB
Go
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,
|
|
})
|
|
}
|