Smail/backend/controllers/email/contacts.go
2026-09-13 04:33:42 +08:00

412 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package email
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"git.pcux.cn/xuans/Smail/db"
"git.pcux.cn/xuans/Smail/dto/response"
"git.pcux.cn/xuans/Smail/models"
"git.pcux.cn/xuans/Smail/utils/address"
"git.pcux.cn/xuans/Smail/utils/context"
log "github.com/sirupsen/logrus"
"xorm.io/xorm"
)
type deleteContactRequest struct {
Email string `json:"email"`
}
type trustSenderRequest struct {
Email string `json:"email"`
Trusted bool `json:"trusted"`
}
type senderFilterRequest struct {
ID int `json:"id"`
Email string `json:"email"`
FilterType string `json:"filter_type"`
}
type toggleSenderFilterRequest struct {
Email string `json:"email"`
TargetType string `json:"target_type"`
}
// ContactsList 查询当前用户的历史写信收件人列表(多租户严格隔离)
func ContactsList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if ctx == nil || ctx.UserID <= 0 {
response.NewErrorResponse(response.NeedLogin, "请先登录", "").FPrint(w)
return
}
var contacts []models.UserContact
err := db.Instance.Table(&models.UserContact{}).
Where("user_id = ?", ctx.UserID).
OrderBy("last_used_at DESC").
Limit(100).
Find(&contacts)
if err != nil {
log.WithContext(ctx).WithError(err).Error("query user contacts failed")
response.NewErrorResponse(response.ServerError, "获取联系人失败", "").FPrint(w)
return
}
response.NewSuccessResponse(contacts).FPrint(w)
}
// DeleteContact 删除当前用户的某项历史联系人记录
func DeleteContact(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if ctx == nil || ctx.UserID <= 0 {
response.NewErrorResponse(response.NeedLogin, "请先登录", "").FPrint(w)
return
}
var reqData deleteContactRequest
if err := json.NewDecoder(req.Body).Decode(&reqData); err != nil {
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
email := strings.ToLower(strings.TrimSpace(reqData.Email))
if email == "" {
response.NewErrorResponse(response.ParamsError, "联系人邮箱不能为空", "").FPrint(w)
return
}
_, err := db.Instance.Table(&models.UserContact{}).
Where("user_id = ? AND LOWER(email) = ?", ctx.UserID, email).
Delete(&models.UserContact{})
if err != nil {
log.WithContext(ctx).WithError(err).Error("delete user contact failed")
response.NewErrorResponse(response.ServerError, "删除联系人失败", "").FPrint(w)
return
}
response.NewSuccessResponse("ok").FPrint(w)
}
// TrustSender 将发件人添加至信任白名单,或从中移除
func TrustSender(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if ctx == nil || ctx.UserID <= 0 {
response.NewErrorResponse(response.NeedLogin, "请先登录", "").FPrint(w)
return
}
var reqData trustSenderRequest
if err := json.NewDecoder(req.Body).Decode(&reqData); err != nil {
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
email := strings.ToLower(strings.TrimSpace(reqData.Email))
if email == "" {
response.NewErrorResponse(response.ParamsError, "发件人邮箱不能为空", "").FPrint(w)
return
}
now := time.Now()
if reqData.Trusted {
var existing models.UserTrustedSender
has, err := db.Instance.Table(&models.UserTrustedSender{}).
Where("user_id = ? AND LOWER(email) = ?", ctx.UserID, email).
Get(&existing)
if err != nil {
log.WithContext(ctx).WithError(err).Error("check trusted sender failed")
response.NewErrorResponse(response.ServerError, "操作失败", "").FPrint(w)
return
}
if has {
if existing.FilterType != models.FilterTypeWhitelist {
existing.FilterType = models.FilterTypeWhitelist
existing.UpdatedAt = &now
_, _ = db.Instance.ID(existing.ID).Cols("filter_type", "updated_at").Update(&existing)
}
} else {
item := models.UserTrustedSender{
UserID: ctx.UserID,
Email: email,
FilterType: models.FilterTypeWhitelist,
CreatedAt: now,
UpdatedAt: &now,
}
if _, err := db.Instance.Insert(&item); err != nil {
log.WithContext(ctx).WithError(err).Error("insert trusted sender failed")
response.NewErrorResponse(response.ServerError, "添加信任发件人失败", "").FPrint(w)
return
}
}
} else {
if _, err := db.Instance.Table(&models.UserTrustedSender{}).
Where("user_id = ? AND LOWER(email) = ?", ctx.UserID, email).
Delete(&models.UserTrustedSender{}); err != nil {
log.WithContext(ctx).WithError(err).Error("remove trusted sender failed")
response.NewErrorResponse(response.ServerError, "添加信任发件人失败", "").FPrint(w)
return
}
}
response.NewSuccessResponse("ok").FPrint(w)
}
// SenderFiltersList 查询当前用户的发件人黑白名单列表(支持分页、搜索、类型筛选与统计)
func SenderFiltersList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if ctx == nil || ctx.UserID <= 0 {
response.NewErrorResponse(response.NeedLogin, "请先登录", "").FPrint(w)
return
}
query := req.URL.Query()
filterType := strings.ToLower(strings.TrimSpace(query.Get("filter_type")))
keyword := strings.ToLower(strings.TrimSpace(query.Get("keyword")))
page, _ := strconv.Atoi(query.Get("page"))
if page <= 0 {
page = 1
}
pageSize, _ := strconv.Atoi(query.Get("page_size"))
if pageSize <= 0 || pageSize > 100 {
pageSize = 20
}
offset := (page - 1) * pageSize
session := db.Instance.Table(&models.UserTrustedSender{}).Where("user_id = ?", ctx.UserID)
if filterType == models.FilterTypeWhitelist || filterType == models.FilterTypeBlacklist {
session = session.And("filter_type = ?", filterType)
}
if keyword != "" {
session = session.And("LOWER(email) LIKE ?", "%"+keyword+"%")
}
var list []models.UserTrustedSender
total, err := session.OrderBy("created_at DESC").Limit(pageSize, offset).FindAndCount(&list)
if err != nil {
log.WithContext(ctx).WithError(err).Error("query sender filters failed")
response.NewErrorResponse(response.ServerError, "查询过滤名单失败", "").FPrint(w)
return
}
whitelistCount, _ := db.Instance.Table(&models.UserTrustedSender{}).
Where("user_id = ? AND filter_type = ?", ctx.UserID, models.FilterTypeWhitelist).
Count(&models.UserTrustedSender{})
blacklistCount, _ := db.Instance.Table(&models.UserTrustedSender{}).
Where("user_id = ? AND filter_type = ?", ctx.UserID, models.FilterTypeBlacklist).
Count(&models.UserTrustedSender{})
response.NewSuccessResponse(map[string]any{
"list": list,
"total": total,
"whitelist_count": whitelistCount,
"blacklist_count": blacklistCount,
"page": page,
"page_size": pageSize,
}).FPrint(w)
}
// AddSenderFilter 添加或更新当前用户的白名单/黑名单
func AddSenderFilter(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if ctx == nil || ctx.UserID <= 0 {
response.NewErrorResponse(response.NeedLogin, "请先登录", "").FPrint(w)
return
}
var reqData senderFilterRequest
if err := json.NewDecoder(req.Body).Decode(&reqData); err != nil {
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
email := strings.ToLower(strings.TrimSpace(reqData.Email))
if email == "" || !address.IsValidEmailAddress(email) {
response.NewErrorResponse(response.ParamsError, "邮箱格式不正确", "").FPrint(w)
return
}
filterType := strings.ToLower(strings.TrimSpace(reqData.FilterType))
if filterType == "" {
filterType = models.FilterTypeWhitelist
}
if filterType != models.FilterTypeWhitelist && filterType != models.FilterTypeBlacklist {
response.NewErrorResponse(response.ParamsError, "名单类别必须为白名单或黑名单", "").FPrint(w)
return
}
now := time.Now()
var existing models.UserTrustedSender
has, err := db.Instance.Table(&models.UserTrustedSender{}).
Where("user_id = ? AND LOWER(email) = ?", ctx.UserID, email).
Get(&existing)
if err != nil {
log.WithContext(ctx).WithError(err).Error("check sender filter exists failed")
response.NewErrorResponse(response.ServerError, "操作失败", "").FPrint(w)
return
}
if has {
existing.FilterType = filterType
existing.UpdatedAt = &now
if _, err := db.Instance.ID(existing.ID).Cols("filter_type", "updated_at").Update(&existing); err != nil {
log.WithContext(ctx).WithError(err).Error("update sender filter failed")
response.NewErrorResponse(response.ServerError, "更新过滤名单失败", "").FPrint(w)
return
}
} else {
item := models.UserTrustedSender{
UserID: ctx.UserID,
Email: email,
FilterType: filterType,
CreatedAt: now,
UpdatedAt: &now,
}
if _, err := db.Instance.Insert(&item); err != nil {
log.WithContext(ctx).WithError(err).Error("insert sender filter failed")
response.NewErrorResponse(response.ServerError, "添加过滤名单失败", "").FPrint(w)
return
}
}
response.NewSuccessResponse("ok").FPrint(w)
}
// DeleteSenderFilter 删除发件人黑白名单条目(支持ID或邮箱地址删除)
func DeleteSenderFilter(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if ctx == nil || ctx.UserID <= 0 {
response.NewErrorResponse(response.NeedLogin, "请先登录", "").FPrint(w)
return
}
var reqData senderFilterRequest
if req.Body != nil {
_ = json.NewDecoder(req.Body).Decode(&reqData)
}
email := strings.ToLower(strings.TrimSpace(reqData.Email))
if reqData.ID <= 0 && email == "" {
if idVal, err := strconv.Atoi(req.URL.Query().Get("id")); err == nil && idVal > 0 {
reqData.ID = idVal
}
if qEmail := strings.ToLower(strings.TrimSpace(req.URL.Query().Get("email"))); qEmail != "" {
email = qEmail
}
}
session := db.Instance.Table(&models.UserTrustedSender{}).Where("user_id = ?", ctx.UserID)
if reqData.ID > 0 {
session = session.And("id = ?", reqData.ID)
} else if email != "" {
session = session.And("LOWER(email) = ?", email)
} else {
response.NewErrorResponse(response.ParamsError, "请指定要删除的记录ID或发件人邮箱", "").FPrint(w)
return
}
if _, err := session.Delete(&models.UserTrustedSender{}); err != nil {
log.WithContext(ctx).WithError(err).Error("delete sender filter failed")
response.NewErrorResponse(response.ServerError, "操作失败", "").FPrint(w)
return
}
response.NewSuccessResponse("ok").FPrint(w)
}
// ToggleSenderFilter 快速切换发件人的白名单/黑名单状态
func ToggleSenderFilter(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if ctx == nil || ctx.UserID <= 0 {
response.NewErrorResponse(response.NeedLogin, "请先登录", "").FPrint(w)
return
}
var reqData toggleSenderFilterRequest
if err := json.NewDecoder(req.Body).Decode(&reqData); err != nil {
response.NewErrorResponse(response.ParamsError, "参数错误", "").FPrint(w)
return
}
email := strings.ToLower(strings.TrimSpace(reqData.Email))
if email == "" || !address.IsValidEmailAddress(email) {
response.NewErrorResponse(response.ParamsError, "邮箱格式不正确", "").FPrint(w)
return
}
now := time.Now()
var existing models.UserTrustedSender
has, err := db.Instance.Table(&models.UserTrustedSender{}).
Where("user_id = ? AND LOWER(email) = ?", ctx.UserID, email).
Get(&existing)
if err != nil {
log.WithContext(ctx).WithError(err).Error("query sender filter for toggle failed")
response.NewErrorResponse(response.ServerError, "操作失败", "").FPrint(w)
return
}
targetType := strings.ToLower(strings.TrimSpace(reqData.TargetType))
if !has {
if targetType != models.FilterTypeWhitelist && targetType != models.FilterTypeBlacklist {
targetType = models.FilterTypeBlacklist
}
item := models.UserTrustedSender{
UserID: ctx.UserID,
Email: email,
FilterType: targetType,
CreatedAt: now,
UpdatedAt: &now,
}
if _, err := db.Instance.Insert(&item); err != nil {
log.WithContext(ctx).WithError(err).Error("insert toggled sender filter failed")
response.NewErrorResponse(response.ServerError, "操作失败", "").FPrint(w)
return
}
} else {
if targetType == "" {
if existing.FilterType == models.FilterTypeBlacklist {
targetType = models.FilterTypeWhitelist
} else {
targetType = models.FilterTypeBlacklist
}
}
existing.FilterType = targetType
existing.UpdatedAt = &now
if _, err := db.Instance.ID(existing.ID).Cols("filter_type", "updated_at").Update(&existing); err != nil {
log.WithContext(ctx).WithError(err).Error("update toggled sender filter failed")
response.NewErrorResponse(response.ServerError, "操作失败", "").FPrint(w)
return
}
}
response.NewSuccessResponse("ok").FPrint(w)
}
// RecordUserContacts 异步批量记录发件成功后的收件人地址
func RecordUserContacts(userID int, recipients []models.UserContact) {
if userID <= 0 || len(recipients) == 0 {
return
}
now := time.Now()
_ = db.Transaction(db.Instance, func(session *xorm.Session) error {
for _, r := range recipients {
email := strings.ToLower(strings.TrimSpace(r.Email))
if email == "" {
continue
}
var existing models.UserContact
has, err := session.Table(&models.UserContact{}).
Where("user_id = ? AND LOWER(email) = ?", userID, email).
Get(&existing)
if err != nil {
return err
}
if has {
existing.LastUsedAt = now
if r.Name != "" {
existing.Name = r.Name
}
if _, err := session.ID(existing.ID).Cols("name", "last_used_at").Update(&existing); err != nil {
return err
}
} else {
item := models.UserContact{
UserID: userID,
Email: email,
Name: r.Name,
LastUsedAt: now,
}
if _, err := session.Insert(&item); err != nil {
return err
}
}
}
return nil
})
}