Files
memory/server/internal/handler/post.go
2025-12-14 20:33:33 +08:00

292 lines
7.6 KiB
Go

package handler
import (
"database/sql"
"net/http"
"strconv"
"memory/internal/config"
"memory/internal/middleware"
"memory/internal/model"
"github.com/gin-gonic/gin"
)
type PostHandler struct {
db *sql.DB
cfg *config.Config
}
func NewPostHandler(db *sql.DB, cfg *config.Config) *PostHandler {
return &PostHandler{db: db, cfg: cfg}
}
func (h *PostHandler) Create(c *gin.Context) {
var req model.CreatePostRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID := middleware.GetUserID(c)
tx, err := h.db.Begin()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
return
}
defer tx.Rollback()
result, err := tx.Exec("INSERT INTO posts (user_id, content) VALUES (?, ?)", userID, req.Content)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create post"})
return
}
postID, _ := result.LastInsertId()
// 关联媒体文件
for i, mediaURL := range req.MediaIDs {
_, err := tx.Exec(
"INSERT INTO post_media (post_id, media_url, media_type, sort_order) VALUES (?, ?, 'image', ?)",
postID, mediaURL, i,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to attach media"})
return
}
}
if err := tx.Commit(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to commit"})
return
}
c.JSON(http.StatusCreated, gin.H{"id": postID})
}
func (h *PostHandler) List(c *gin.Context) {
userID := middleware.GetUserID(c)
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
offset := (page - 1) * pageSize
rows, err := h.db.Query(`
SELECT p.id, p.user_id, p.content, p.created_at,
u.id, u.username, u.nickname, u.avatar_url,
(SELECT COUNT(*) FROM likes WHERE post_id = p.id) as like_count,
(SELECT COUNT(*) FROM likes WHERE post_id = p.id AND user_id = ?) as liked,
(SELECT COUNT(*) FROM comments WHERE post_id = p.id) as comment_count
FROM posts p
JOIN users u ON p.user_id = u.id
ORDER BY p.created_at DESC
LIMIT ? OFFSET ?
`, userID, pageSize, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
return
}
defer rows.Close()
posts := []model.Post{}
for rows.Next() {
var post model.Post
var user model.User
var liked int
err := rows.Scan(
&post.ID, &post.UserID, &post.Content, &post.CreatedAt,
&user.ID, &user.Username, &user.Nickname, &user.AvatarURL,
&post.LikeCount, &liked, &post.CommentCount,
)
if err != nil {
continue
}
post.User = &user
post.Liked = liked > 0
// 获取媒体
post.Media = h.getPostMedia(post.ID)
// 获取表情反应
post.Reactions = h.getPostReactions(post.ID, userID)
posts = append(posts, post)
}
c.JSON(http.StatusOK, posts)
}
func (h *PostHandler) Get(c *gin.Context) {
postID, _ := strconv.ParseInt(c.Param("id"), 10, 64)
userID := middleware.GetUserID(c)
var post model.Post
var user model.User
var liked int
err := h.db.QueryRow(`
SELECT p.id, p.user_id, p.content, p.created_at,
u.id, u.username, u.nickname, u.avatar_url,
(SELECT COUNT(*) FROM likes WHERE post_id = p.id) as like_count,
(SELECT COUNT(*) FROM likes WHERE post_id = p.id AND user_id = ?) as liked,
(SELECT COUNT(*) FROM comments WHERE post_id = p.id) as comment_count
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.id = ?
`, userID, postID).Scan(
&post.ID, &post.UserID, &post.Content, &post.CreatedAt,
&user.ID, &user.Username, &user.Nickname, &user.AvatarURL,
&post.LikeCount, &liked, &post.CommentCount,
)
if err == sql.ErrNoRows {
c.JSON(http.StatusNotFound, gin.H{"error": "post not found"})
return
}
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
return
}
post.User = &user
post.Liked = liked > 0
post.Media = h.getPostMedia(post.ID)
post.Reactions = h.getPostReactions(post.ID, userID)
c.JSON(http.StatusOK, post)
}
func (h *PostHandler) Delete(c *gin.Context) {
postID, _ := strconv.ParseInt(c.Param("id"), 10, 64)
userID := middleware.GetUserID(c)
isAdmin, _ := c.Get("is_admin")
// 检查权限
var postUserID int64
err := h.db.QueryRow("SELECT user_id FROM posts WHERE id = ?", postID).Scan(&postUserID)
if err == sql.ErrNoRows {
c.JSON(http.StatusNotFound, gin.H{"error": "post not found"})
return
}
if postUserID != userID && !isAdmin.(bool) {
c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"})
return
}
_, err = h.db.Exec("DELETE FROM posts WHERE id = ?", postID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete post"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
}
func (h *PostHandler) Like(c *gin.Context) {
postID, _ := strconv.ParseInt(c.Param("id"), 10, 64)
userID := middleware.GetUserID(c)
_, err := h.db.Exec("INSERT OR IGNORE INTO likes (post_id, user_id) VALUES (?, ?)", postID, userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to like"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "liked"})
}
func (h *PostHandler) Unlike(c *gin.Context) {
postID, _ := strconv.ParseInt(c.Param("id"), 10, 64)
userID := middleware.GetUserID(c)
_, err := h.db.Exec("DELETE FROM likes WHERE post_id = ? AND user_id = ?", postID, userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to unlike"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "unliked"})
}
func (h *PostHandler) AddReaction(c *gin.Context) {
postID, _ := strconv.ParseInt(c.Param("id"), 10, 64)
userID := middleware.GetUserID(c)
var req model.AddReactionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
_, err := h.db.Exec(
"INSERT OR IGNORE INTO reactions (post_id, user_id, emoji) VALUES (?, ?, ?)",
postID, userID, req.Emoji,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to add reaction"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "reaction added"})
}
func (h *PostHandler) RemoveReaction(c *gin.Context) {
postID, _ := strconv.ParseInt(c.Param("id"), 10, 64)
userID := middleware.GetUserID(c)
emoji := c.Query("emoji")
_, err := h.db.Exec(
"DELETE FROM reactions WHERE post_id = ? AND user_id = ? AND emoji = ?",
postID, userID, emoji,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to remove reaction"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "reaction removed"})
}
func (h *PostHandler) getPostMedia(postID int64) []model.Media {
rows, err := h.db.Query(
"SELECT id, post_id, media_url, media_type, sort_order FROM post_media WHERE post_id = ? ORDER BY sort_order",
postID,
)
if err != nil {
return nil
}
defer rows.Close()
var media []model.Media
for rows.Next() {
var m model.Media
rows.Scan(&m.ID, &m.PostID, &m.MediaURL, &m.MediaType, &m.SortOrder)
media = append(media, m)
}
return media
}
func (h *PostHandler) getPostReactions(postID, userID int64) []model.ReactionGroup {
rows, err := h.db.Query(`
SELECT emoji, COUNT(*) as count,
SUM(CASE WHEN user_id = ? THEN 1 ELSE 0 END) as reacted
FROM reactions WHERE post_id = ?
GROUP BY emoji
ORDER BY count DESC
`, userID, postID)
if err != nil {
return nil
}
defer rows.Close()
var reactions []model.ReactionGroup
for rows.Next() {
var r model.ReactionGroup
var reacted int
rows.Scan(&r.Emoji, &r.Count, &reacted)
r.Reacted = reacted > 0
reactions = append(reactions, r)
}
return reactions
}