292 lines
7.6 KiB
Go
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
|
|
}
|