Files
kefu/im/backend/internal/app/extensions.go
T
2026-08-27 14:04:28 +08:00

204 lines
8.0 KiB
Go

package app
import (
"database/sql"
"net/http"
"strings"
)
func (a *App) searchUsers(w http.ResponseWriter, r *http.Request) {
keyword := strings.TrimSpace(r.URL.Query().Get("q"))
if keyword == "" {
reply(w, map[string]any{"items": []profileView{}, "total": 0})
return
}
pattern := "%" + keyword + "%"
rows, err := a.db.QueryContext(r.Context(), `SELECT u.id FROM users u JOIN user_profiles p ON p.user_id=u.id WHERE u.status=1 AND u.id<>? AND (p.nickname LIKE ? OR u.public_id LIKE ?) AND NOT EXISTS(SELECT 1 FROM user_blocks b WHERE (b.user_id=? AND b.blocked_user_id=u.id) OR (b.user_id=u.id AND b.blocked_user_id=?)) ORDER BY p.is_vip DESC,p.last_active_at DESC LIMIT 50`, current(r).ID, pattern, pattern, current(r).ID, current(r).ID)
if err != nil {
fail(w, 500, 50001, "搜索失败")
return
}
defer rows.Close()
items := []profileView{}
for rows.Next() {
var id int64
_ = rows.Scan(&id)
if item, loadErr := a.loadProfile(r, id, current(r).ID); loadErr == nil {
items = append(items, item)
}
}
reply(w, map[string]any{"items": items, "total": len(items), "keyword": keyword})
}
func (a *App) followingList(w http.ResponseWriter, r *http.Request) {
a.relationshipList(w, r, "following")
}
func (a *App) followerList(w http.ResponseWriter, r *http.Request) {
a.relationshipList(w, r, "followers")
}
func (a *App) visitorList(w http.ResponseWriter, r *http.Request) {
a.relationshipList(w, r, "visitors")
}
func (a *App) relationshipList(w http.ResponseWriter, r *http.Request, listType string) {
var rows *sql.Rows
var err error
switch listType {
case "following":
rows, err = a.db.QueryContext(r.Context(), `SELECT target_user_id FROM user_follows WHERE user_id=? ORDER BY created_at DESC LIMIT 100`, current(r).ID)
case "followers":
rows, err = a.db.QueryContext(r.Context(), `SELECT user_id FROM user_follows WHERE target_user_id=? ORDER BY created_at DESC LIMIT 100`, current(r).ID)
default:
rows, err = a.db.QueryContext(r.Context(), `SELECT viewer_user_id FROM profile_visits WHERE target_user_id=? GROUP BY viewer_user_id ORDER BY MAX(visited_at) DESC LIMIT 100`, current(r).ID)
}
if err != nil {
fail(w, 500, 50001, "查询失败")
return
}
defer rows.Close()
items := []profileView{}
for rows.Next() {
var id int64
_ = rows.Scan(&id)
if item, loadErr := a.loadProfile(r, id, current(r).ID); loadErr == nil {
items = append(items, item)
}
}
reply(w, map[string]any{"items": items, "total": len(items), "type": listType})
}
type privacyView struct {
NearbyVisible bool `json:"nearbyVisible"`
DistanceVisible bool `json:"distanceVisible"`
OnlineVisible bool `json:"onlineVisible"`
LastActiveVisible bool `json:"lastActiveVisible"`
AllowStrangerMessage bool `json:"allowStrangerMessage"`
AllowProfileVisitRecord bool `json:"allowProfileVisitRecord"`
AllowSearch bool `json:"allowSearch"`
}
func (a *App) getPrivacy(w http.ResponseWriter, r *http.Request) {
var values [7]int
err := a.db.QueryRowContext(r.Context(), `SELECT nearby_visible,distance_visible,online_visible,last_active_visible,allow_stranger_message,allow_profile_visit_record,allow_search FROM user_privacy_settings WHERE user_id=?`, current(r).ID).Scan(&values[0], &values[1], &values[2], &values[3], &values[4], &values[5], &values[6])
if err != nil {
fail(w, 500, 50001, "查询失败")
return
}
reply(w, privacyView{NearbyVisible: values[0] == 1, DistanceVisible: values[1] == 1, OnlineVisible: values[2] == 1, LastActiveVisible: values[3] == 1, AllowStrangerMessage: values[4] == 1, AllowProfileVisitRecord: values[5] == 1, AllowSearch: values[6] == 1})
}
func (a *App) updatePrivacy(w http.ResponseWriter, r *http.Request) {
var req privacyView
if decode(r, &req) != nil {
fail(w, 400, 20001, "隐私设置格式错误")
return
}
_, err := a.db.ExecContext(r.Context(), `UPDATE user_privacy_settings SET nearby_visible=?,distance_visible=?,online_visible=?,last_active_visible=?,allow_stranger_message=?,allow_profile_visit_record=?,allow_search=? WHERE user_id=?`, req.NearbyVisible, req.DistanceVisible, req.OnlineVisible, req.LastActiveVisible, req.AllowStrangerMessage, req.AllowProfileVisitRecord, req.AllowSearch, current(r).ID)
if err != nil {
fail(w, 500, 50001, "保存失败")
return
}
reply(w, req)
}
func (a *App) blockUser(w http.ResponseWriter, r *http.Request) {
target, err := pathID(r)
if err != nil || target == current(r).ID {
fail(w, 400, 20001, "无效用户")
return
}
tx, _ := a.db.BeginTx(r.Context(), nil)
_, err = tx.ExecContext(r.Context(), `INSERT IGNORE INTO user_blocks(user_id,blocked_user_id,reason)VALUES(?,?,'user_action')`, current(r).ID, target)
if err == nil {
_, _ = tx.ExecContext(r.Context(), `DELETE FROM user_follows WHERE (user_id=? AND target_user_id=?) OR (user_id=? AND target_user_id=?)`, current(r).ID, target, target, current(r).ID)
_, _ = tx.ExecContext(r.Context(), `DELETE FROM user_likes WHERE (user_id=? AND target_user_id=?) OR (user_id=? AND target_user_id=?)`, current(r).ID, target, target, current(r).ID)
err = tx.Commit()
}
if err != nil {
_ = tx.Rollback()
fail(w, 500, 50001, "拉黑失败")
return
}
reply(w, map[string]bool{"success": true})
}
func (a *App) unblockUser(w http.ResponseWriter, r *http.Request) {
target, err := pathID(r)
if err != nil {
fail(w, 400, 20001, "无效用户")
return
}
_, err = a.db.ExecContext(r.Context(), `DELETE FROM user_blocks WHERE user_id=? AND blocked_user_id=?`, current(r).ID, target)
if err != nil {
fail(w, 500, 50001, "解除拉黑失败")
return
}
reply(w, map[string]bool{"success": true})
}
func (a *App) loadPost(r *http.Request, id int64) (postView, error) {
var item postView
var userID int64
var vip int
err := a.db.QueryRowContext(r.Context(), `SELECT p.id,p.user_id,p.content,p.location_text,p.like_count,p.comment_count,p.created_at,u.public_id,pr.nickname,pr.avatar_url,pr.gender,pr.is_vip,EXISTS(SELECT 1 FROM post_likes l WHERE l.post_id=p.id AND l.user_id=?) FROM posts p JOIN users u ON u.id=p.user_id JOIN user_profiles pr ON pr.user_id=p.user_id WHERE p.id=? AND p.status=1 AND (p.visibility=1 OR p.user_id=? OR (p.visibility=2 AND EXISTS(SELECT 1 FROM user_follows audience WHERE audience.user_id=? AND audience.target_user_id=p.user_id)))`, current(r).ID, id, current(r).ID, current(r).ID).Scan(&item.ID, &userID, &item.Content, &item.Location, &item.LikeCount, &item.CommentCount, &item.CreatedAt, &item.User.PublicID, &item.User.Nickname, &item.User.Avatar, &item.User.Gender, &vip, &item.Liked)
if err != nil {
return item, err
}
item.User.ID = userID
item.User.VIP = vip == 1
item.Media = []string{}
rows, _ := a.db.QueryContext(r.Context(), `SELECT media_url FROM post_media WHERE post_id=? ORDER BY sort_order`, id)
if rows != nil {
defer rows.Close()
for rows.Next() {
var media string
_ = rows.Scan(&media)
item.Media = append(item.Media, media)
}
}
return item, nil
}
func (a *App) postDetail(w http.ResponseWriter, r *http.Request) {
id, err := pathID(r)
if err != nil {
fail(w, 400, 20001, "动态编号无效")
return
}
item, err := a.loadPost(r, id)
if err != nil {
fail(w, 404, 30001, "动态不存在")
return
}
reply(w, item)
}
func (a *App) userPosts(w http.ResponseWriter, r *http.Request) {
userID, err := pathID(r)
if err != nil {
fail(w, 400, 20001, "用户编号无效")
return
}
rows, err := a.db.QueryContext(r.Context(), `SELECT id FROM posts WHERE user_id=? AND status=1 AND (visibility=1 OR user_id=? OR (visibility=2 AND EXISTS(SELECT 1 FROM user_follows audience WHERE audience.user_id=? AND audience.target_user_id=posts.user_id))) ORDER BY created_at DESC LIMIT 100`, userID, current(r).ID, current(r).ID)
if err != nil {
fail(w, 500, 50001, "查询失败")
return
}
defer rows.Close()
ids := []int64{}
for rows.Next() {
var id int64
_ = rows.Scan(&id)
ids = append(ids, id)
}
items := []postView{}
for _, id := range ids {
if item, loadErr := a.loadPost(r, id); loadErr == nil {
items = append(items, item)
}
}
reply(w, map[string]any{"items": items, "total": len(items), "userId": userID})
}