Smail/server/services/list/list.go

366 lines
11 KiB
Go

package list
import (
"fmt"
"git.pcux.cn/xuans/Smail/consts"
"git.pcux.cn/xuans/Smail/db"
"git.pcux.cn/xuans/Smail/dto"
"git.pcux.cn/xuans/Smail/dto/response"
"git.pcux.cn/xuans/Smail/models"
"git.pcux.cn/xuans/Smail/utils/context"
"git.pcux.cn/xuans/Smail/utils/query"
)
import . "xorm.io/builder"
func GetEmailList(ctx *context.Context, tagInfo dto.SearchTag, keyword string, pop3List bool, offset, limit int) (emailList []*response.EmailResponseData, total int64, err error) {
return getList(ctx, tagInfo, keyword, pop3List, offset, limit)
}
func getList(ctx *context.Context, tagInfo dto.SearchTag, keyword string, pop3List bool, offset, limit int) (emailList []*response.EmailResponseData, total int64, err error) {
querySQL, queryParams := genSQL(ctx, false, tagInfo, keyword, pop3List, offset, limit)
if err := db.Instance.SQL(querySQL, queryParams...).Find(&emailList); err != nil {
return nil, 0, fmt.Errorf("query email list: %w", err)
}
totalSQL, totalParams := genSQL(ctx, true, tagInfo, keyword, pop3List, offset, limit)
if _, err := db.Instance.SQL(totalSQL, totalParams...).Get(&total); err != nil {
return nil, 0, fmt.Errorf("query email count: %w", err)
}
return emailList, total, nil
}
func genSQL(ctx *context.Context, count bool, tagInfo dto.SearchTag, keyword string, pop3List bool, offset, limit int) (string, []any) {
sqlParams := []any{ctx.UserID}
sql := "select "
if count {
sql += `count(1) from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? `
} else if pop3List {
sql += `e.id,e.size from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? `
} else {
sql += `e.*,ue.is_read from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? `
}
if tagInfo.Status != -1 {
switch tagInfo.Status {
case consts.EmailStatusDel:
sql += " and (ue.status =? or ue.group_id=?) "
sqlParams = append(sqlParams, tagInfo.Status, models.Deleted)
case consts.EmailStatusDrafts:
sql += " and (ue.status =? or ue.group_id=?) "
sqlParams = append(sqlParams, tagInfo.Status, models.Drafts)
case consts.EmailStatusJunk:
sql += " and (ue.status =? or ue.group_id=?) "
sqlParams = append(sqlParams, tagInfo.Status, models.Junk)
}
} else if tagInfo.Status == -1 {
if tagInfo.Type != 1 {
sql += " and ue.status = 0"
} else {
// 发件箱不展示已删除的邮件
sql += " and ue.status != 3"
}
}
if tagInfo.Type == consts.EmailTypeReceive {
sql += " and type =? "
sqlParams = append(sqlParams, tagInfo.Type)
} else if tagInfo.Type == consts.EmailTypeSend {
sql += " and (type =? or ue.group_id=?)"
sqlParams = append(sqlParams, tagInfo.Type, models.Sent)
}
if tagInfo.GroupId != -1 {
if tagInfo.GroupId > 0 {
sql += " and ue.group_id=? "
sqlParams = append(sqlParams, tagInfo.GroupId)
} else if tagInfo.GroupId == 0 && tagInfo.Status == -1 {
sql += " and (ue.group_id=? or ue.group_id=0)"
sqlParams = append(sqlParams, models.INBOX)
}
} else {
sql += " and (ue.group_id=0 or ue.group_id =?) "
sqlParams = append(sqlParams, models.INBOX)
}
if keyword != "" {
sql += " and (subject like ? or text like ? )"
sqlParams = append(sqlParams, "%"+keyword+"%", "%"+keyword+"%")
}
if limit == 0 {
limit = 10
}
sql += " order by e.id desc"
if limit < 10000 {
sql += fmt.Sprintf(" LIMIT %d OFFSET %d ", limit, offset)
}
return sql, sqlParams
}
type statRes struct {
Total int64
Size int64
}
// Stat 查询邮件总数和大小
func Stat(ctx *context.Context) (int64, int64, error) {
sql := `select count(1) as total,sum(size) as size from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? and e.type = 0 and ue.status != 3`
var ret statRes
if _, err := db.Instance.SQL(sql, ctx.UserID).Get(&ret); err != nil {
return 0, 0, fmt.Errorf("query mailbox statistics: %w", err)
}
return ret.Total, ret.Size, nil
}
type ImapListReq struct {
UidList []int
Star int
End int
}
func GetUEListByUID(ctx *context.Context, groupName string, star, end int, uidList []int) ([]*response.UserEmailUIDData, error) {
var ue []*response.UserEmailUIDData
sql := "SELECT id,email_id, is_read, ROW_NUMBER() OVER (ORDER BY id) AS serial_number FROM `user_email` WHERE user_id = ? "
params := []any{ctx.UserID}
if len(uidList) > 0 {
placeholders, uidArgs, ok := query.IntList(uidList, 10000)
if !ok {
return nil, fmt.Errorf("invalid UID list")
}
sql += fmt.Sprintf(" and id in (%s)", placeholders)
params = append(params, uidArgs...)
}
if star > 0 {
sql += " and id >=?"
params = append(params, star)
}
if end > 0 {
sql += " and id <=?"
params = append(params, end)
}
switch groupName {
case "INBOX":
sql += " and status =? and group_id = 0"
params = append(params, 0)
case "Sent Messages":
sql += " and status =? and group_id = 0"
params = append(params, 1)
case "Drafts":
sql += " and status =? and group_id = 0"
params = append(params, 4)
case "Deleted Messages":
sql += " and status =? and group_id = 0"
params = append(params, 3)
case "Junk":
sql += " and status =? and group_id = 0"
params = append(params, 5)
default:
var group models.Group
found, err := db.Instance.Table("group").Where("user_id=? and full_path=?", ctx.UserID, groupName).Get(&group)
if err != nil {
return nil, err
}
if !found || group.ID == 0 {
return nil, nil
}
sql += " and group_id = ?"
params = append(params, group.ID)
}
if err := db.Instance.SQL(sql, params...).Find(&ue); err != nil {
return nil, err
}
return ue, nil
}
func getEmailListByUidList(ctx *context.Context, groupName string, req ImapListReq) ([]*response.EmailResponseData, error) {
var ret []*response.EmailResponseData
var ue []*response.UserEmailUIDData
if req.Star < 0 || req.End < 0 || (req.End > 0 && req.Star > req.End) {
return nil, fmt.Errorf("invalid UID range")
}
buildQuery := func(filterColumn string, baseParams []any) (string, []any, bool) {
sql := "SELECT id,email_id,is_read,ROW_NUMBER() OVER (ORDER BY id) AS serial_number FROM `user_email` WHERE user_id = ?"
params := append([]any{}, baseParams...)
if filterColumn != "" {
sql += " and " + filterColumn + " = ?"
if filterColumn == "status" {
sql += " and group_id = 0"
}
}
if req.Star > 0 {
sql += " and id >= ?"
params = append(params, req.Star)
if req.End > 0 {
sql += " and id <= ?"
params = append(params, req.End)
}
return sql, params, true
}
placeholders, uidArgs, ok := query.IntList(req.UidList, 10000)
if !ok {
return "", nil, false
}
sql += fmt.Sprintf(" and id in (%s)", placeholders)
params = append(params, uidArgs...)
return sql, params, true
}
var sql string
var params []any
var ok bool
switch groupName {
case "INBOX":
sql, params, ok = buildQuery("status", []any{ctx.UserID, 0})
case "Sent Messages":
sql, params, ok = buildQuery("status", []any{ctx.UserID, 1})
case "Drafts":
sql, params, ok = buildQuery("status", []any{ctx.UserID, 4})
case "Deleted Messages":
sql, params, ok = buildQuery("status", []any{ctx.UserID, 3})
case "Junk":
sql, params, ok = buildQuery("status", []any{ctx.UserID, 5})
default:
var group models.Group
found, err := db.Instance.Table("group").Where("user_id=? and full_path=?", ctx.UserID, groupName).Get(&group)
if err != nil {
return nil, fmt.Errorf("query mailbox: %w", err)
}
if !found || group.ID == 0 {
return nil, fmt.Errorf("mailbox not found")
}
sql, params, ok = buildQuery("group_id", []any{ctx.UserID, group.ID})
}
if !ok {
return nil, fmt.Errorf("invalid UID list")
}
if err := db.Instance.SQL(sql, params...).Find(&ue); err != nil {
return nil, fmt.Errorf("query mailbox UID list: %w", err)
}
ueMap := map[int]*response.UserEmailUIDData{}
var emailIds []int
for _, email := range ue {
ueMap[email.EmailID] = email
emailIds = append(emailIds, email.EmailID)
}
if len(emailIds) == 0 {
return ret, nil
}
if err := db.Instance.Table("email").Select("*").Where(Eq{"id": emailIds}).Find(&ret); err != nil {
return nil, fmt.Errorf("query mailbox messages: %w", err)
}
for i, data := range ret {
item := ueMap[data.Id]
if item == nil {
continue
}
ret[i].IsRead = item.IsRead
ret[i].SerialNumber = item.SerialNumber
ret[i].UeId = item.ID
}
return ret, nil
}
func GetEmailListByGroup(ctx *context.Context, groupName string, req ImapListReq, uid bool) ([]*response.EmailResponseData, error) {
if len(req.UidList) == 0 && req.Star == 0 && req.End == 0 {
return nil, nil
}
if uid {
return getEmailListByUidList(ctx, groupName, req)
}
if req.Star < 0 || req.End < 0 || (req.End > 0 && req.Star > req.End) {
return nil, fmt.Errorf("invalid sequence range")
}
var ret []*response.EmailResponseData
var ue []*response.UserEmailUIDData
inner := "SELECT id,email_id,is_read,ROW_NUMBER() OVER (ORDER BY id) AS serial_number FROM `user_email` WHERE user_id = ?"
params := []any{ctx.UserID}
switch groupName {
case "INBOX":
inner += " and status = ? and group_id = 0"
params = append(params, 0)
case "Sent Messages":
inner += " and status = ? and group_id = 0"
params = append(params, 1)
case "Drafts":
inner += " and status = ? and group_id = 0"
params = append(params, 4)
case "Deleted Messages":
inner += " and status = ? and group_id = 0"
params = append(params, 3)
case "Junk":
inner += " and status = ? and group_id = 0"
params = append(params, 5)
default:
var group models.Group
found, err := db.Instance.Table("group").Where("user_id=? and full_path=?", ctx.UserID, groupName).Get(&group)
if err != nil {
return nil, fmt.Errorf("query mailbox: %w", err)
}
if !found || group.ID == 0 {
return nil, fmt.Errorf("mailbox not found")
}
inner += " and group_id = ?"
params = append(params, group.ID)
}
sql := "SELECT * FROM (" + inner + ") a WHERE "
if req.Star > 0 {
sql += "serial_number >= ?"
params = append(params, req.Star)
if req.End > 0 {
sql += " and serial_number <= ?"
params = append(params, req.End)
}
} else {
placeholders, sequenceArgs, ok := query.IntList(req.UidList, 10000)
if !ok {
return nil, fmt.Errorf("invalid sequence list")
}
sql += fmt.Sprintf("serial_number in (%s)", placeholders)
params = append(params, sequenceArgs...)
}
if err := db.Instance.SQL(sql, params...).Find(&ue); err != nil {
return nil, fmt.Errorf("query mailbox sequence list: %w", err)
}
ueMap := map[int]*response.UserEmailUIDData{}
var emailIds []int
for _, email := range ue {
ueMap[email.EmailID] = email
emailIds = append(emailIds, email.EmailID)
}
if len(emailIds) == 0 {
return ret, nil
}
if err := db.Instance.Table("email").Select("*").Where(Eq{"id": emailIds}).Find(&ret); err != nil {
return nil, fmt.Errorf("query mailbox messages: %w", err)
}
for i, data := range ret {
item := ueMap[data.Id]
if item == nil {
continue
}
ret[i].IsRead = item.IsRead
ret[i].SerialNumber = item.SerialNumber
ret[i].UeId = item.ID
}
return ret, nil
}