feat: add im home and message action flows
This commit is contained in:
+230
-11
@@ -953,7 +953,10 @@ func (subsMapper) Delete(tenantID types.TenantID, topic string, user types.Uid)
|
||||
|
||||
// MessagesPersistenceInterface is an interface which defines methods for persistent storage of messages.
|
||||
type MessagesPersistenceInterface interface {
|
||||
Save(tenantID types.TenantID, msg *types.Message, attachmentURLs []string, readBySender bool) (error, bool)
|
||||
Save(tenantID types.TenantID, msg *types.Message, attachmentURLs []string, readBySender bool) (*types.MessageSaveResult, error)
|
||||
GetOne(tenantID types.TenantID, topic string, seq int) (*types.Message, error)
|
||||
Recall(tenantID types.TenantID, topic string, seq int, actor types.Uid, expectedVersion int, recalledAt time.Time) (*types.Message, error)
|
||||
Edit(tenantID types.TenantID, topic string, seq int, actor types.Uid, expectedVersion int, head types.KVMap, content any, reason string, editedAt time.Time) (*types.Message, error)
|
||||
DeleteList(tenantID types.TenantID, topic string, delID int, forUser types.Uid, msgDelAge time.Duration, ranges []types.Range) error
|
||||
GetAll(tenantID types.TenantID, topic string, forUser types.Uid, opt *types.QueryOpt) ([]types.Message, error)
|
||||
GetDeleted(tenantID types.TenantID, topic string, forUser types.Uid, opt *types.QueryOpt) ([]types.Range, int, error)
|
||||
@@ -965,33 +968,84 @@ type messagesMapper struct{}
|
||||
// Messages is a singleton ancor object for exporting MessagesPersistenceInterface.
|
||||
var Messages MessagesPersistenceInterface
|
||||
|
||||
// Save message
|
||||
func (messagesMapper) Save(tenantID types.TenantID, msg *types.Message, attachmentURLs []string, readBySender bool) (error, bool) {
|
||||
if msg == nil || msg.TenantID != tenantID {
|
||||
return types.ErrMalformed, false
|
||||
func (messagesMapper) GetOne(tenantID types.TenantID, topic string, seq int) (*types.Message, error) {
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMessageGetOne(tenantID, topic, seq)
|
||||
}
|
||||
|
||||
func (messagesMapper) Recall(tenantID types.TenantID, topic string, seq int, actor types.Uid, expectedVersion int, recalledAt time.Time) (*types.Message, error) {
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMessageRecall(tenantID, topic, seq, actor, expectedVersion, recalledAt)
|
||||
}
|
||||
|
||||
func (messagesMapper) Edit(tenantID types.TenantID, topic string, seq int, actor types.Uid, expectedVersion int, head types.KVMap, content any, reason string, editedAt time.Time) (*types.Message, error) {
|
||||
if err := validateMessageHeaders(head); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return err, false
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantMessageEdit(tenantID, topic, seq, actor, expectedVersion, head, content, reason, editedAt)
|
||||
}
|
||||
|
||||
// Save message
|
||||
func (messagesMapper) Save(tenantID types.TenantID, msg *types.Message, attachmentURLs []string, readBySender bool) (*types.MessageSaveResult, error) {
|
||||
if msg == nil || msg.TenantID != tenantID {
|
||||
return nil, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientMsgID, err := normalizeClientMsgID(msg.Head)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.ClientMsgID = clientMsgID
|
||||
fromUid := types.ParseUid(msg.From)
|
||||
if clientMsgID != "" {
|
||||
duplicate, err := tenantAdp.TenantMessageGetByClientID(tenantID, fromUid, clientMsgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if duplicate != nil {
|
||||
msg.ObjHeader = duplicate.ObjHeader
|
||||
msg.DeletedAt = duplicate.DeletedAt
|
||||
msg.DelId = duplicate.DelId
|
||||
msg.DeletedFor = duplicate.DeletedFor
|
||||
msg.SeqId = duplicate.SeqId
|
||||
msg.Topic = duplicate.Topic
|
||||
msg.From = duplicate.From
|
||||
msg.ClientMsgID = duplicate.ClientMsgID
|
||||
msg.Head = duplicate.Head
|
||||
msg.Content = duplicate.Content
|
||||
return &types.MessageSaveResult{SeqId: duplicate.SeqId, Duplicate: true}, nil
|
||||
}
|
||||
}
|
||||
msg.InitTimes()
|
||||
msg.SetUid(Store.GetUid())
|
||||
// Increment topic's or user's SeqId
|
||||
err = tenantAdp.TenantTopicUpdateOnMessage(tenantID, msg.Topic, msg)
|
||||
if err != nil {
|
||||
return err, false
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = tenantAdp.TenantMessageSave(tenantID, msg)
|
||||
if err != nil {
|
||||
return err, false
|
||||
return nil, err
|
||||
}
|
||||
|
||||
markedReadBySender := false
|
||||
// Mark message as read by the sender.
|
||||
if readBySender {
|
||||
// Make sure From is valid, otherwise we will reset values for all subscribers.
|
||||
fromUid := types.ParseUid(msg.From)
|
||||
if !fromUid.IsZero() {
|
||||
// Ignore the error here. It's not a big deal if it fails.
|
||||
if subErr := tenantAdp.TenantSubsUpdate(tenantID, msg.Topic, fromUid,
|
||||
@@ -1014,11 +1068,74 @@ func (messagesMapper) Save(tenantID types.TenantID, msg *types.Message, attachme
|
||||
}
|
||||
}
|
||||
if len(attachments) > 0 {
|
||||
return tenantAdp.TenantFileLinkAttachments(tenantID, "", types.ZeroUid, msg.Uid(), attachments), markedReadBySender
|
||||
if err := tenantAdp.TenantFileLinkAttachments(tenantID, "", types.ZeroUid, msg.Uid(), attachments); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, markedReadBySender
|
||||
return &types.MessageSaveResult{SeqId: msg.SeqId, MarkedReadBySender: markedReadBySender}, nil
|
||||
}
|
||||
|
||||
func normalizeClientMsgID(head types.KVMap) (string, error) {
|
||||
if head == nil {
|
||||
return "", nil
|
||||
}
|
||||
if err := validateMessageHeaders(head); err != nil {
|
||||
return "", err
|
||||
}
|
||||
value, ok := head["x-im-client-mid"]
|
||||
if !ok {
|
||||
return "", nil
|
||||
}
|
||||
clientMsgID, ok := value.(string)
|
||||
if !ok {
|
||||
return "", types.ErrMalformed
|
||||
}
|
||||
clientMsgID = strings.TrimSpace(clientMsgID)
|
||||
if len(clientMsgID) > 64 {
|
||||
return "", types.ErrMalformed
|
||||
}
|
||||
return clientMsgID, nil
|
||||
}
|
||||
|
||||
func validateMessageHeaders(head types.KVMap) error {
|
||||
if schema, ok := head["x-im-schema"]; ok {
|
||||
value, ok := schema.(string)
|
||||
if !ok || !validIMMessageHeaderToken(value, 32) {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
}
|
||||
if msgType, ok := head["type"]; ok {
|
||||
value, ok := msgType.(string)
|
||||
if !ok || !validIMMessageType(value) {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validIMMessageHeaderToken(value string, max int) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || len(value) > max {
|
||||
return false
|
||||
}
|
||||
for _, ch := range value {
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '.' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func validIMMessageType(value string) bool {
|
||||
switch strings.TrimSpace(value) {
|
||||
case "text", "image", "file", "audio", "video", "location", "contact", "card", "call", "system":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteList deletes multiple messages defined by a list of ranges.
|
||||
@@ -1469,6 +1586,16 @@ var Verifications VerificationPersistenceInterface
|
||||
type UserSecurityPersistenceInterface interface {
|
||||
GetAuthVersion(tenantID types.TenantID, uid types.Uid) (int, error)
|
||||
BumpAuthVersion(tenantID types.TenantID, uid types.Uid, updatedAt time.Time) (int, error)
|
||||
GetRealName(tenantID types.TenantID, uid types.Uid) (*types.RealNameVerification, error)
|
||||
CreateRealNameSubmission(tenantID types.TenantID, rv *types.RealNameVerification) error
|
||||
CreateActionToken(tenantID types.TenantID, token *types.ActionToken) error
|
||||
ConsumeActionToken(tenantID types.TenantID, uid types.Uid, token, action, target string, now time.Time) (*types.ActionToken, error)
|
||||
GetIndependentPassword(tenantID types.TenantID, uid types.Uid, purpose string) (*types.IndependentPassword, error)
|
||||
UpsertIndependentPassword(tenantID types.TenantID, pass *types.IndependentPassword) error
|
||||
UpdateIndependentPasswordAttempts(tenantID types.TenantID, uid types.Uid, purpose string, attempts int, lockedUntil *time.Time, updatedAt time.Time) error
|
||||
CreateSystemNotification(tenantID types.TenantID, notification *types.SystemNotification) error
|
||||
ListSystemNotifications(tenantID types.TenantID, uid types.Uid, cursor string, limit int) ([]types.SystemNotification, string, error)
|
||||
MarkSystemNotificationRead(tenantID types.TenantID, uid types.Uid, eventID string, readAt time.Time) error
|
||||
}
|
||||
|
||||
type userSecurityMapper struct{}
|
||||
@@ -1492,6 +1619,98 @@ func (userSecurityMapper) BumpAuthVersion(tenantID types.TenantID, uid types.Uid
|
||||
return tenantAdp.TenantUserSecurityBumpAuthVersion(tenantID, uid, updatedAt)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) GetRealName(tenantID types.TenantID, uid types.Uid) (*types.RealNameVerification, error) {
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantRealNameGet(tenantID, uid)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) CreateRealNameSubmission(tenantID types.TenantID, rv *types.RealNameVerification) error {
|
||||
if rv == nil || rv.TenantID != tenantID {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tenantAdp.TenantRealNameCreate(tenantID, rv)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) CreateActionToken(tenantID types.TenantID, token *types.ActionToken) error {
|
||||
if token == nil || token.TenantID != tenantID {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tenantAdp.TenantActionTokenCreate(tenantID, token)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) ConsumeActionToken(tenantID types.TenantID, uid types.Uid, token, action, target string, now time.Time) (*types.ActionToken, error) {
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantActionTokenConsume(tenantID, uid, token, action, target, now)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) GetIndependentPassword(tenantID types.TenantID, uid types.Uid, purpose string) (*types.IndependentPassword, error) {
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tenantAdp.TenantIndependentPasswordGet(tenantID, uid, purpose)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) UpsertIndependentPassword(tenantID types.TenantID, pass *types.IndependentPassword) error {
|
||||
if pass == nil || pass.TenantID != tenantID {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tenantAdp.TenantIndependentPasswordUpsert(tenantID, pass)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) UpdateIndependentPasswordAttempts(tenantID types.TenantID, uid types.Uid, purpose string, attempts int, lockedUntil *time.Time, updatedAt time.Time) error {
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tenantAdp.TenantIndependentPasswordUpdateAttempts(tenantID, uid, purpose, attempts, lockedUntil, updatedAt)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) CreateSystemNotification(tenantID types.TenantID, notification *types.SystemNotification) error {
|
||||
if notification == nil || notification.TenantID != tenantID {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tenantAdp.TenantSystemNotificationCreate(tenantID, notification)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) ListSystemNotifications(tenantID types.TenantID, uid types.Uid, cursor string, limit int) ([]types.SystemNotification, string, error) {
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return tenantAdp.TenantSystemNotificationList(tenantID, uid, cursor, limit)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) MarkSystemNotificationRead(tenantID types.TenantID, uid types.Uid, eventID string, readAt time.Time) error {
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tenantAdp.TenantSystemNotificationMarkRead(tenantID, uid, eventID, readAt)
|
||||
}
|
||||
|
||||
func (verificationMapper) Create(tenantID types.TenantID, vc *types.VerificationCode) error {
|
||||
if vc == nil || vc.TenantID != tenantID {
|
||||
return types.ErrMalformed
|
||||
|
||||
Reference in New Issue
Block a user