spark-server/internal/module/favorite/logic/create.go
2026-09-18 17:14:38 +08:00

118 lines
3.4 KiB
Go

package logic
import (
"context"
"encoding/json"
"errors"
"time"
"spark-server/internal/module/favorite/entity"
msgentity "spark-server/internal/module/message/entity"
"spark-server/internal/pkg/errs"
"spark-server/internal/pkg/reqctx"
"spark-server/internal/pkg/validate"
)
// CreateInput favorites an existing message. Expired chat objects fail; no client re-upload.
type CreateInput struct {
MsgID int64 `json:"msg_id" validate:"required,gt=0" label:"field.msg_id" errmsg:"required:validate.favorite.msg_required;gt:validate.favorite.msg_invalid"`
}
// Item is the HTTP/TCP favorite view. Content is the snapshot JSON (perm/fav keys).
type Item struct {
ID int64 `json:"id"`
MsgID int64 `json:"msg_id"`
ConversationID int64 `json:"conversation_id"`
MessageType msgentity.Type `json:"message_type"`
Content json.RawMessage `json:"content"`
CreatedAt int64 `json:"created_at"`
}
func (s *Service) Create(ctx context.Context, userID int64, in *CreateInput) (*Item, error) {
if !s.ready() || userID <= 0 {
return nil, errs.ErrUnauthorized
}
if in == nil {
return nil, errs.ErrInvalidArgument
}
if err := validate.StructLang(reqctx.Lang(ctx), in); err != nil {
return nil, err
}
if existing, err := s.repo.GetByUserMsg(ctx, userID, in.MsgID); err == nil {
return itemOf(existing), nil
} else if !errors.Is(err, errs.ErrNotFound) {
return nil, err
}
msg, payload, err := s.loadFavoriteSource(ctx, userID, in.MsgID)
if err != nil {
return nil, err
}
copied, err := s.copyMedia(ctx, payload)
if err != nil {
return nil, err
}
fav, err := s.persistFavorite(ctx, userID, msg, payload)
if err != nil {
s.deleteKeys(ctx, copied)
if errors.Is(err, errs.ErrDuplicate) {
existing, getErr := s.repo.GetByUserMsg(ctx, userID, in.MsgID)
if getErr == nil {
return itemOf(existing), nil
}
}
return nil, err
}
return itemOf(fav), nil
}
func (s *Service) loadFavoriteSource(ctx context.Context, userID, msgID int64) (*msgentity.Message, *msgentity.Content, error) {
msg, err := s.msgs.GetByID(ctx, msgID)
if err != nil {
return nil, nil, err
}
if msg.Status == msgentity.StatusHidden {
return nil, nil, errs.ErrNotFound
}
if _, err := s.members.GetMember(ctx, msg.ConversationID, userID); err != nil {
if errors.Is(err, errs.ErrNotFound) {
return nil, nil, errs.ErrForbidden
}
return nil, nil, err
}
payload, err := msg.Payload()
if err != nil || payload == nil {
return nil, nil, errs.ErrInvalidArgument
}
return msg, payload, nil
}
func (s *Service) persistFavorite(ctx context.Context, userID int64, msg *msgentity.Message, payload *msgentity.Content) (*entity.Favorite, error) {
raw, err := payload.Marshal()
if err != nil {
return nil, errs.ErrInvalidArgument
}
id, err := s.ids.NextID()
if err != nil {
return nil, err
}
now := time.Now()
fav := &entity.Favorite{
ID: id, UserID: userID, MsgID: msg.ID, ConversationID: msg.ConversationID,
MessageType: int16(msg.Type), Content: raw, CreatedAtMS: now.UnixMilli(),
}
if err := s.repo.Create(ctx, fav); err != nil {
return nil, err
}
return fav, nil
}
func itemOf(fav *entity.Favorite) *Item {
if fav == nil {
return nil
}
return &Item{
ID: fav.ID, MsgID: fav.MsgID, ConversationID: fav.ConversationID,
MessageType: msgentity.Type(fav.MessageType), Content: fav.Content, CreatedAt: fav.CreatedAtMS,
}
}