412 lines
14 KiB
Go
412 lines
14 KiB
Go
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
|
||
})
|
||
}
|