Compare commits

...

4 Commits

Author SHA1 Message Date
Abhishek Pandey
5649c93eda Minor fix 2024-01-12 18:08:08 -08:00
Abhishek Pandey
e1f569d4e6 Store previous post ID in details 2024-01-12 18:06:03 -08:00
Abhishek Pandey
4dcb820201 Linter 2024-01-12 11:56:00 -08:00
Abhishek Pandey
7b193a06a0 Augment post details with recipient information 2024-01-12 11:42:09 -08:00
5 changed files with 56 additions and 11 deletions

View File

@ -19,15 +19,19 @@ var _ backupHandler[models.Conversationable, models.Postable] = &conversationsBa
type conversationsBackupHandler struct { type conversationsBackupHandler struct {
ac api.Conversations ac api.Conversations
protectedResource string protectedResource string
// SMTP address for the group
resourceEmail string
} }
func NewConversationBackupHandler( func NewConversationBackupHandler(
protectedResource string, protectedResource string,
ac api.Conversations, ac api.Conversations,
email string,
) conversationsBackupHandler { ) conversationsBackupHandler {
return conversationsBackupHandler{ return conversationsBackupHandler{
ac: ac, ac: ac,
protectedResource: protectedResource, protectedResource: protectedResource,
resourceEmail: email,
} }
} }
@ -120,13 +124,17 @@ func (bh conversationsBackupHandler) getItem(
containerIDs path.Elements, // expects: [conversationID, threadID] containerIDs path.Elements, // expects: [conversationID, threadID]
postID string, postID string,
) (models.Postable, *details.GroupsInfo, error) { ) (models.Postable, *details.GroupsInfo, error) {
cc := api.CallConfig{
Expand: []string{"inReplyTo"},
}
return bh.ac.GetConversationPost( return bh.ac.GetConversationPost(
ctx, ctx,
groupID, groupID,
containerIDs[0], containerIDs[0],
containerIDs[1], containerIDs[1],
postID, postID,
api.CallConfig{}) cc)
} }
//lint:ignore U1000 false linter issue due to generics //lint:ignore U1000 false linter issue due to generics
@ -134,6 +142,13 @@ func (bh conversationsBackupHandler) augmentItemInfo(
dgi *details.GroupsInfo, dgi *details.GroupsInfo,
c models.Conversationable, c models.Conversationable,
) { ) {
// Posts are always sent to the group email address, along with additional
// recipients if any. Currently we don't have a way to get the unique
// recipient list for individual posts due to graph api limitations.
//
// Store the group mail address so that we can use it to populate the 'To'
// field during Post -> EML exports.
dgi.Post.Recipients = []string{bh.resourceEmail}
dgi.Post.Topic = ptr.Val(c.GetTopic()) dgi.Post.Topic = ptr.Val(c.GetTopic())
} }

View File

@ -3,6 +3,7 @@ package groups
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/kopia/kopia/repo/manifest" "github.com/kopia/kopia/repo/manifest"
@ -319,10 +320,17 @@ func backupConversations(
counter *count.Bus, counter *count.Bus,
errs *fault.Bus, errs *fault.Bus,
) ([]data.BackupCollection, error) { ) ([]data.BackupCollection, error) {
groupEmail := strings.Clone(ptr.Val(bc.group.GetMail()))
// This is unlikely, but if it does happen in the wild, we should investigate it.
if len(groupEmail) == 0 {
return nil, clues.New("group has no mail address")
}
var ( var (
bh = groups.NewConversationBackupHandler( bh = groups.NewConversationBackupHandler(
bc.producerConfig.ProtectedResource.ID(), bc.producerConfig.ProtectedResource.ID(),
bc.apiCli.Conversations()) bc.apiCli.Conversations(),
groupEmail)
colls []data.BackupCollection colls []data.BackupCollection
) )

View File

@ -61,11 +61,13 @@ type GroupsInfo struct {
} }
type ConversationPostInfo struct { type ConversationPostInfo struct {
CreatedAt time.Time `json:"createdAt,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"`
Creator string `json:"creator,omitempty"` Creator string `json:"creator,omitempty"`
Preview string `json:"preview,omitempty"` Preview string `json:"preview,omitempty"`
Size int64 `json:"size,omitempty"` Recipients []string `json:"recipients,omitempty"`
Topic string `json:"topic,omitempty"` InReplyTo string `json:"inReplyTo,omitempty"`
Size int64 `json:"size,omitempty"`
Topic string `json:"topic,omitempty"`
} }
type ChannelMessageInfo struct { type ChannelMessageInfo struct {

View File

@ -69,8 +69,25 @@ func (c Conversations) GetConversationPost(
preview = "malformed or unparseable content body: " + preview preview = "malformed or unparseable content body: " + preview
} }
var inReplyToID string
prevPost := post.GetInReplyTo()
if prevPost != nil {
inReplyToID = ptr.Val(prevPost.GetId())
}
// Set prev post to nil to avoid storing it again in the backup. Storage is unnecessary
// since this is a read only property and graph doesn't support POSTing it. This is
// also safe to do since we do a full enumeration every time, so post and all its
// ancestors are guaranteed to exist.
//
// All we need to persist here is the prev post ID here so that we can build the
// reply tree during restore operation and restore posts top to bottom using
// POST /groups/{id}/conversations/{id}/threads/{id}/posts/{id}/reply
post.SetInReplyTo(nil)
if !ptr.Val(post.GetHasAttachments()) && !HasAttachments(post.GetBody()) { if !ptr.Val(post.GetHasAttachments()) && !HasAttachments(post.GetBody()) {
return post, conversationPostInfo(post, contentLen, preview), nil return post, conversationPostInfo(post, contentLen, preview, inReplyToID), nil
} }
attachments, totalSize, err := c.getAttachments( attachments, totalSize, err := c.getAttachments(
@ -95,7 +112,9 @@ func (c Conversations) GetConversationPost(
post.SetAttachments(attachments) post.SetAttachments(attachments)
return post, conversationPostInfo(post, contentLen, preview), graph.Stack(ctx, err).OrNil() return post,
conversationPostInfo(post, contentLen, preview, inReplyToID),
graph.Stack(ctx, err).OrNil()
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -105,7 +124,7 @@ func (c Conversations) GetConversationPost(
func conversationPostInfo( func conversationPostInfo(
post models.Postable, post models.Postable,
size int64, size int64,
preview string, preview, inReplyToID string,
) *details.GroupsInfo { ) *details.GroupsInfo {
if post == nil { if post == nil {
return nil return nil
@ -120,6 +139,7 @@ func conversationPostInfo(
CreatedAt: ptr.Val(post.GetCreatedDateTime()), CreatedAt: ptr.Val(post.GetCreatedDateTime()),
Creator: sender, Creator: sender,
Preview: preview, Preview: preview,
InReplyTo: inReplyToID,
Size: size, Size: size,
} }

View File

@ -109,7 +109,7 @@ func (suite *ConversationsAPIUnitSuite) TestConversationPostInfo() {
t := suite.T() t := suite.T()
post, expected := test.postAndInfo() post, expected := test.postAndInfo()
result := conversationPostInfo(post, 0, "") result := conversationPostInfo(post, 0, "", "")
assert.Equal(t, expected, result) assert.Equal(t, expected, result)
}) })