250 lines
11 KiB
Go
250 lines
11 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 + "%"
|
|
page, pageSize, offset := pageOptions(r)
|
|
var total int
|
|
if err := a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM users u JOIN user_profiles p ON p.user_id=u.id JOIN user_privacy_settings privacy ON privacy.user_id=u.id WHERE u.status=1 AND u.id<>? AND privacy.allow_search=1 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=?))`, current(r).ID, pattern, pattern, current(r).ID, current(r).ID).Scan(&total); err != nil {
|
|
fail(w, 500, 50001, "搜索失败")
|
|
return
|
|
}
|
|
rows, err := a.db.QueryContext(r.Context(), `SELECT u.id FROM users u JOIN user_profiles p ON p.user_id=u.id JOIN user_privacy_settings privacy ON privacy.user_id=u.id WHERE u.status=1 AND u.id<>? AND privacy.allow_search=1 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 ? OFFSET ?`, current(r).ID, pattern, pattern, current(r).ID, current(r).ID, pageSize, offset)
|
|
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 {
|
|
item.AvatarThumbnail = avatarThumbnailURL(item.Avatar)
|
|
items = append(items, item)
|
|
}
|
|
}
|
|
reply(w, map[string]any{"items": items, "total": total, "keyword": keyword, "page": page, "pageSize": pageSize, "hasMore": offset+len(items) < total})
|
|
}
|
|
|
|
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) {
|
|
if !a.resolveMembershipEntitlements(r.Context(), current(r).ID).CanViewVisitors {
|
|
fail(w, http.StatusForbidden, 10006, "开通会员后可查看访客记录")
|
|
return
|
|
}
|
|
a.relationshipList(w, r, "visitors")
|
|
}
|
|
|
|
func (a *App) relationshipList(w http.ResponseWriter, r *http.Request, listType string) {
|
|
var rows *sql.Rows
|
|
var err error
|
|
page, pageSize, offset := pageOptions(r)
|
|
var total int
|
|
switch listType {
|
|
case "following":
|
|
_ = a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM user_follows WHERE user_id=?`, current(r).ID).Scan(&total)
|
|
rows, err = a.db.QueryContext(r.Context(), `SELECT target_user_id FROM user_follows WHERE user_id=? ORDER BY created_at DESC LIMIT ? OFFSET ?`, current(r).ID, pageSize, offset)
|
|
case "followers":
|
|
_ = a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM user_follows WHERE target_user_id=?`, current(r).ID).Scan(&total)
|
|
rows, err = a.db.QueryContext(r.Context(), `SELECT user_id FROM user_follows WHERE target_user_id=? ORDER BY created_at DESC LIMIT ? OFFSET ?`, current(r).ID, pageSize, offset)
|
|
default:
|
|
_ = a.db.QueryRowContext(r.Context(), `SELECT COUNT(DISTINCT viewer_user_id) FROM profile_visits WHERE target_user_id=?`, current(r).ID).Scan(&total)
|
|
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 ? OFFSET ?`, current(r).ID, pageSize, offset)
|
|
}
|
|
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 {
|
|
item.AvatarThumbnail = avatarThumbnailURL(item.Avatar)
|
|
items = append(items, item)
|
|
}
|
|
}
|
|
reply(w, map[string]any{"items": items, "total": total, "type": listType, "page": page, "pageSize": pageSize, "hasMore": offset+len(items) < total})
|
|
}
|
|
|
|
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"`
|
|
InvisibleVisit bool `json:"invisibleVisit"`
|
|
AllowSearch bool `json:"allowSearch"`
|
|
}
|
|
|
|
func (a *App) getPrivacy(w http.ResponseWriter, r *http.Request) {
|
|
var values [8]int
|
|
err := a.db.QueryRowContext(r.Context(), `SELECT nearby_visible,distance_visible,online_visible,last_active_visible,allow_stranger_message,allow_profile_visit_record,invisible_visit,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], &values[7])
|
|
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, InvisibleVisit: values[6] == 1, AllowSearch: values[7] == 1})
|
|
}
|
|
|
|
func (a *App) updatePrivacy(w http.ResponseWriter, r *http.Request) {
|
|
var req privacyView
|
|
if decode(r, &req) != nil {
|
|
fail(w, 400, 20001, "隐私设置格式错误")
|
|
return
|
|
}
|
|
if req.InvisibleVisit && !a.resolveMembershipEntitlements(r.Context(), current(r).ID).CanInvisibleVisit {
|
|
fail(w, http.StatusForbidden, 10006, "当前会员等级不支持隐身访问")
|
|
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=?,invisible_visit=?,allow_search=? WHERE user_id=?`, req.NearbyVisible, req.DistanceVisible, req.OnlineVisible, req.LastActiveVisible, req.AllowStrangerMessage, req.AllowProfileVisitRecord, req.InvisibleVisit, 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
|
|
}
|
|
var targetExists int
|
|
if err = a.db.QueryRowContext(r.Context(), `SELECT EXISTS(SELECT 1 FROM users WHERE id=? AND status=1 AND deleted_at IS NULL)`, target).Scan(&targetExists); err != nil || targetExists != 1 {
|
|
fail(w, http.StatusNotFound, 30001, "用户不存在")
|
|
return
|
|
}
|
|
tx, err := a.db.BeginTx(r.Context(), nil)
|
|
if err != nil {
|
|
fail(w, 500, 50001, "拉黑失败")
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
_, 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 {
|
|
_, err = 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)
|
|
}
|
|
if err == nil {
|
|
_, err = 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)
|
|
}
|
|
if err == nil {
|
|
err = tx.Commit()
|
|
}
|
|
if err != nil {
|
|
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.User.AvatarThumbnail = avatarThumbnailURL(item.User.Avatar)
|
|
item.CanEdit = userID == current(r).ID
|
|
item.CanDelete = userID == current(r).ID
|
|
item.Media = []string{}
|
|
item.MediaThumbnails = []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)
|
|
item.MediaThumbnails = append(item.MediaThumbnails, mediaThumbnailURL(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
|
|
}
|
|
page, pageSize, offset := pageOptions(r)
|
|
var total int
|
|
if err := a.db.QueryRowContext(r.Context(), `SELECT COUNT(*) FROM posts p WHERE p.user_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)))`, userID, current(r).ID, current(r).ID).Scan(&total); err != nil {
|
|
fail(w, 500, 50001, "查询失败")
|
|
return
|
|
}
|
|
rows, err := a.db.QueryContext(r.Context(), `SELECT p.id FROM posts p WHERE p.user_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))) ORDER BY p.created_at DESC LIMIT ? OFFSET ?`, userID, current(r).ID, current(r).ID, pageSize, offset)
|
|
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": total, "userId": userID, "page": page, "pageSize": pageSize, "hasMore": offset+len(items) < total})
|
|
}
|