move ad-hoc drive api calls into m365/api (#3451)
ensure that all drive-baased graph client calls exist in the m365/api package, not defined ad-hoc throughout the codebase. --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🧹 Tech Debt/Cleanup #### Issue(s) * #1996 #### Test Plan - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
be4032aec9
commit
7181e2ef90
@ -21,6 +21,7 @@ import (
|
||||
"github.com/alcionai/corso/src/pkg/fault"
|
||||
"github.com/alcionai/corso/src/pkg/logger"
|
||||
"github.com/alcionai/corso/src/pkg/services/m365"
|
||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||
)
|
||||
|
||||
var purgeCmd = &cobra.Command{
|
||||
@ -190,12 +191,11 @@ func purgeOneDriveFolders(
|
||||
return clues.New("non-OneDrive item")
|
||||
}
|
||||
|
||||
return onedrive.DeleteItem(
|
||||
return api.DeleteDriveItem(
|
||||
ctx,
|
||||
gs,
|
||||
*driveFolder.GetParentReference().GetDriveId(),
|
||||
*f.GetId(),
|
||||
)
|
||||
*f.GetId())
|
||||
}
|
||||
|
||||
return purgeFolders(ctx, gc, boundary, "OneDrive Folders", uid, getter, deleter)
|
||||
|
||||
@ -233,7 +233,7 @@ func (gc *GraphConnector) ConsumeRestoreCollections(
|
||||
|
||||
switch sels.Service {
|
||||
case selectors.ServiceExchange:
|
||||
status, err = exchange.RestoreExchangeDataCollections(ctx, creds, gc.Service, dest, dcs, deets, errs)
|
||||
status, err = exchange.RestoreCollections(ctx, creds, gc.Discovery, gc.Service, dest, dcs, deets, errs)
|
||||
case selectors.ServiceOneDrive:
|
||||
status, err = onedrive.RestoreCollections(ctx, creds, backupVersion, gc.Service, dest, opts, dcs, deets, errs)
|
||||
case selectors.ServiceSharePoint:
|
||||
|
||||
@ -1,24 +1,35 @@
|
||||
package exchange
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"fmt"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||
"github.com/alcionai/corso/src/internal/connector/support"
|
||||
"github.com/alcionai/corso/src/pkg/logger"
|
||||
)
|
||||
|
||||
type attachmentPoster interface {
|
||||
PostSmallAttachment(
|
||||
ctx context.Context,
|
||||
userID, containerID, itemID string,
|
||||
body models.Attachmentable,
|
||||
) error
|
||||
PostLargeAttachment(
|
||||
ctx context.Context,
|
||||
userID, containerID, itemID, name string,
|
||||
size int64,
|
||||
body models.Attachmentable,
|
||||
) (models.UploadSessionable, error)
|
||||
}
|
||||
|
||||
const (
|
||||
// Use large attachment logic for attachments > 3MB
|
||||
// https://learn.microsoft.com/en-us/graph/outlook-large-attachments
|
||||
largeAttachmentSize = int32(3 * 1024 * 1024)
|
||||
attachmentChunkSize = 4 * 1024 * 1024
|
||||
fileAttachmentOdataValue = "#microsoft.graph.fileAttachment"
|
||||
itemAttachmentOdataValue = "#microsoft.graph.itemAttachment"
|
||||
referenceAttachmentOdataValue = "#microsoft.graph.referenceAttachment"
|
||||
@ -43,23 +54,30 @@ func attachmentType(attachment models.Attachmentable) models.AttachmentType {
|
||||
// uploadAttachment will upload the specified message attachment to M365
|
||||
func uploadAttachment(
|
||||
ctx context.Context,
|
||||
uploader attachmentUploadable,
|
||||
cli attachmentPoster,
|
||||
userID, containerID, parentItemID string,
|
||||
attachment models.Attachmentable,
|
||||
) error {
|
||||
attachmentType := attachmentType(attachment)
|
||||
var (
|
||||
attachmentType = attachmentType(attachment)
|
||||
id = ptr.Val(attachment.GetId())
|
||||
name = ptr.Val(attachment.GetName())
|
||||
size = ptr.Val(attachment.GetSize())
|
||||
)
|
||||
|
||||
ctx = clues.Add(
|
||||
ctx,
|
||||
"attachment_size", ptr.Val(attachment.GetSize()),
|
||||
"attachment_id", ptr.Val(attachment.GetId()),
|
||||
"attachment_name", clues.Hide(ptr.Val(attachment.GetName())),
|
||||
"attachment_size", size,
|
||||
"attachment_id", id,
|
||||
"attachment_name", clues.Hide(name),
|
||||
"attachment_type", attachmentType,
|
||||
"internal_item_type", getItemAttachmentItemType(attachment),
|
||||
"uploader_item_id", uploader.getItemID())
|
||||
"attachment_odata_type", ptr.Val(attachment.GetOdataType()),
|
||||
"attachment_outlook_odata_type", getOutlookOdataType(attachment),
|
||||
"parent_item_id", parentItemID)
|
||||
|
||||
logger.Ctx(ctx).Debug("uploading attachment")
|
||||
|
||||
// Reference attachments that are inline() do not need to be recreated. The contents are part of the body.
|
||||
// reference attachments that are inline() do not need to be recreated. The contents are part of the body.
|
||||
if attachmentType == models.REFERENCE_ATTACHMENTTYPE && ptr.Val(attachment.GetIsInline()) {
|
||||
logger.Ctx(ctx).Debug("skip uploading inline reference attachment")
|
||||
return nil
|
||||
@ -69,67 +87,32 @@ func uploadAttachment(
|
||||
if attachmentType == models.ITEM_ATTACHMENTTYPE {
|
||||
a, err := support.ToItemAttachment(attachment)
|
||||
if err != nil {
|
||||
logger.CtxErr(ctx, err).Info("item attachment restore not supported for this type. skipping upload.")
|
||||
|
||||
logger.CtxErr(ctx, err).Info(fmt.Sprintf("item attachment type not supported: %v", attachmentType))
|
||||
return nil
|
||||
}
|
||||
|
||||
attachment = a
|
||||
}
|
||||
|
||||
// For Item/Reference attachments *or* file attachments < 3MB, use the attachments endpoint
|
||||
if attachmentType != models.FILE_ATTACHMENTTYPE || ptr.Val(attachment.GetSize()) < largeAttachmentSize {
|
||||
return uploader.uploadSmallAttachment(ctx, attachment)
|
||||
// for file attachments sized >= 3MB
|
||||
if attachmentType == models.FILE_ATTACHMENTTYPE && size >= largeAttachmentSize {
|
||||
_, err := cli.PostLargeAttachment(ctx, userID, containerID, parentItemID, name, int64(size), attachment)
|
||||
return err
|
||||
}
|
||||
|
||||
return uploadLargeAttachment(ctx, uploader, attachment)
|
||||
// for all other attachments
|
||||
return cli.PostSmallAttachment(ctx, userID, containerID, parentItemID, attachment)
|
||||
}
|
||||
|
||||
// uploadLargeAttachment will upload the specified attachment by creating an upload session and
|
||||
// doing a chunked upload
|
||||
func uploadLargeAttachment(
|
||||
ctx context.Context,
|
||||
uploader attachmentUploadable,
|
||||
attachment models.Attachmentable,
|
||||
) error {
|
||||
bs, err := GetAttachmentBytes(attachment)
|
||||
if err != nil {
|
||||
return clues.Stack(err).WithClues(ctx)
|
||||
}
|
||||
|
||||
size := int64(len(bs))
|
||||
|
||||
session, err := uploader.uploadSession(ctx, ptr.Val(attachment.GetName()), size)
|
||||
if err != nil {
|
||||
return clues.Stack(err).WithClues(ctx)
|
||||
}
|
||||
|
||||
url := ptr.Val(session.GetUploadUrl())
|
||||
aw := graph.NewLargeItemWriter(uploader.getItemID(), url, size)
|
||||
logger.Ctx(ctx).Debugw("uploading large attachment", "attachment_url", graph.LoggableURL(url))
|
||||
|
||||
// Upload the stream data
|
||||
copyBuffer := make([]byte, attachmentChunkSize)
|
||||
|
||||
_, err = io.CopyBuffer(aw, bytes.NewReader(bs), copyBuffer)
|
||||
if err != nil {
|
||||
return clues.Wrap(err, "uploading large attachment").WithClues(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getItemAttachmentItemType(query models.Attachmentable) string {
|
||||
empty := ""
|
||||
func getOutlookOdataType(query models.Attachmentable) string {
|
||||
attachment, ok := query.(models.ItemAttachmentable)
|
||||
|
||||
if !ok {
|
||||
return empty
|
||||
return ""
|
||||
}
|
||||
|
||||
item := attachment.GetItem()
|
||||
if item == nil {
|
||||
return empty
|
||||
return ""
|
||||
}
|
||||
|
||||
return ptr.Val(item.GetOdataType())
|
||||
|
||||
@ -1,144 +0,0 @@
|
||||
package exchange
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/users"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||
)
|
||||
|
||||
// attachmentUploadable represents structs that are able to upload small attachments directly to an item or use an
|
||||
// upload session to connect large attachments to their corresponding M365 item.
|
||||
type attachmentUploadable interface {
|
||||
uploadSmallAttachment(ctx context.Context, attachment models.Attachmentable) error
|
||||
uploadSession(ctx context.Context, attachName string, attachSize int64) (models.UploadSessionable, error)
|
||||
// getItemID returns the M365ID of the item associated with the attachment
|
||||
getItemID() string
|
||||
}
|
||||
|
||||
var (
|
||||
_ attachmentUploadable = &mailAttachmentUploader{}
|
||||
_ attachmentUploadable = &eventAttachmentUploader{}
|
||||
)
|
||||
|
||||
// mailAttachmentUploader is a struct that is able to upload attachments for exchange.Mail objects
|
||||
type mailAttachmentUploader struct {
|
||||
userID string
|
||||
folderID string
|
||||
itemID string
|
||||
service graph.Servicer
|
||||
}
|
||||
|
||||
func (mau *mailAttachmentUploader) getItemID() string {
|
||||
return mau.itemID
|
||||
}
|
||||
|
||||
func (mau *mailAttachmentUploader) uploadSmallAttachment(ctx context.Context, attach models.Attachmentable) error {
|
||||
_, err := mau.service.Client().
|
||||
Users().
|
||||
ByUserId(mau.userID).
|
||||
MailFolders().
|
||||
ByMailFolderId(mau.folderID).
|
||||
Messages().
|
||||
ByMessageId(mau.itemID).
|
||||
Attachments().
|
||||
Post(ctx, attach, nil)
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mau *mailAttachmentUploader) uploadSession(
|
||||
ctx context.Context,
|
||||
attachmentName string,
|
||||
attachmentSize int64,
|
||||
) (models.UploadSessionable, error) {
|
||||
session := users.NewItemMailFoldersItemMessagesItemAttachmentsCreateUploadSessionPostRequestBody()
|
||||
session.SetAttachmentItem(makeSessionAttachment(attachmentName, attachmentSize))
|
||||
|
||||
r, err := mau.
|
||||
service.
|
||||
Client().
|
||||
Users().
|
||||
ByUserId(mau.userID).
|
||||
MailFolders().
|
||||
ByMailFolderId(mau.folderID).
|
||||
Messages().
|
||||
ByMessageId(mau.itemID).
|
||||
Attachments().
|
||||
CreateUploadSession().
|
||||
Post(ctx, session, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "uploading mail attachment")
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// eventAttachmentUploader is a struct capable of uploading attachments for exchange.Event objects
|
||||
type eventAttachmentUploader struct {
|
||||
userID string
|
||||
calendarID string
|
||||
itemID string
|
||||
service graph.Servicer
|
||||
}
|
||||
|
||||
func (eau *eventAttachmentUploader) getItemID() string {
|
||||
return eau.itemID
|
||||
}
|
||||
|
||||
func (eau *eventAttachmentUploader) uploadSmallAttachment(ctx context.Context, attach models.Attachmentable) error {
|
||||
_, err := eau.service.Client().
|
||||
Users().
|
||||
ByUserId(eau.userID).
|
||||
Calendars().
|
||||
ByCalendarId(eau.calendarID).
|
||||
Events().
|
||||
ByEventId(eau.itemID).
|
||||
Attachments().
|
||||
Post(ctx, attach, nil)
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eau *eventAttachmentUploader) uploadSession(
|
||||
ctx context.Context,
|
||||
attachmentName string,
|
||||
attachmentSize int64,
|
||||
) (models.UploadSessionable, error) {
|
||||
session := users.NewItemCalendarEventsItemAttachmentsCreateUploadSessionPostRequestBody()
|
||||
session.SetAttachmentItem(makeSessionAttachment(attachmentName, attachmentSize))
|
||||
|
||||
r, err := eau.service.Client().
|
||||
Users().
|
||||
ByUserId(eau.userID).
|
||||
Calendars().
|
||||
ByCalendarId(eau.calendarID).
|
||||
Events().
|
||||
ByEventId(eau.itemID).
|
||||
Attachments().
|
||||
CreateUploadSession().
|
||||
Post(ctx, session, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "uploading event attachment")
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func makeSessionAttachment(name string, size int64) *models.AttachmentItem {
|
||||
attItem := models.NewAttachmentItem()
|
||||
attType := models.FILE_ATTACHMENTTYPE
|
||||
attItem.SetAttachmentType(&attType)
|
||||
attItem.SetName(&name)
|
||||
attItem.SetSize(&size)
|
||||
|
||||
return attItem
|
||||
}
|
||||
@ -79,10 +79,10 @@ func (suite *RestoreIntgSuite) TestRestoreContact() {
|
||||
assert.NoError(t, err, clues.ToCore(err))
|
||||
}()
|
||||
|
||||
info, err := RestoreExchangeContact(
|
||||
info, err := RestoreContact(
|
||||
ctx,
|
||||
exchMock.ContactBytes("Corso TestContact"),
|
||||
suite.gs,
|
||||
suite.ac.Contacts(),
|
||||
control.Copy,
|
||||
folderID,
|
||||
userID)
|
||||
@ -135,9 +135,11 @@ func (suite *RestoreIntgSuite) TestRestoreEvent() {
|
||||
ctx, flush := tester.NewContext(t)
|
||||
defer flush()
|
||||
|
||||
info, err := RestoreExchangeEvent(
|
||||
info, err := RestoreEvent(
|
||||
ctx,
|
||||
test.bytes,
|
||||
suite.ac.Events(),
|
||||
suite.ac.Events(),
|
||||
suite.gs,
|
||||
control.Copy,
|
||||
calendarID,
|
||||
@ -365,11 +367,12 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
||||
defer flush()
|
||||
|
||||
destination := test.destination(t, ctx)
|
||||
info, err := RestoreExchangeObject(
|
||||
info, err := RestoreItem(
|
||||
ctx,
|
||||
test.bytes,
|
||||
test.category,
|
||||
control.Copy,
|
||||
suite.ac,
|
||||
service,
|
||||
destination,
|
||||
userID,
|
||||
|
||||
@ -25,15 +25,24 @@ import (
|
||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||
)
|
||||
|
||||
// RestoreExchangeObject directs restore pipeline towards restore function
|
||||
type itemPoster[T any] interface {
|
||||
PostItem(
|
||||
ctx context.Context,
|
||||
userID, dirID string,
|
||||
body T,
|
||||
) (T, error)
|
||||
}
|
||||
|
||||
// RestoreItem directs restore pipeline towards restore function
|
||||
// based on the path.CategoryType. All input params are necessary to perform
|
||||
// the type-specific restore function.
|
||||
func RestoreExchangeObject(
|
||||
func RestoreItem(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
category path.CategoryType,
|
||||
policy control.CollisionPolicy,
|
||||
service graph.Servicer,
|
||||
ac api.Client,
|
||||
gs graph.Servicer,
|
||||
destination, user string,
|
||||
errs *fault.Bus,
|
||||
) (*details.ExchangeInfo, error) {
|
||||
@ -43,26 +52,21 @@ func RestoreExchangeObject(
|
||||
|
||||
switch category {
|
||||
case path.EmailCategory:
|
||||
return RestoreMailMessage(ctx, bits, service, control.Copy, destination, user, errs)
|
||||
return RestoreMessage(ctx, bits, ac.Mail(), ac.Mail(), gs, control.Copy, destination, user, errs)
|
||||
case path.ContactsCategory:
|
||||
return RestoreExchangeContact(ctx, bits, service, control.Copy, destination, user)
|
||||
return RestoreContact(ctx, bits, ac.Contacts(), control.Copy, destination, user)
|
||||
case path.EventsCategory:
|
||||
return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user, errs)
|
||||
return RestoreEvent(ctx, bits, ac.Events(), ac.Events(), gs, control.Copy, destination, user, errs)
|
||||
default:
|
||||
return nil, clues.Wrap(clues.New(category.String()), "not supported for Exchange restore")
|
||||
}
|
||||
}
|
||||
|
||||
// RestoreExchangeContact restores a contact to the @bits byte
|
||||
// representation of M365 contact object.
|
||||
// @destination M365 ID representing a M365 Contact_Folder
|
||||
// Returns an error if the input bits do not parse into a models.Contactable object
|
||||
// or if an error is encountered sending data to the M365 account.
|
||||
// Post details: https://docs.microsoft.com/en-us/graph/api/user-post-contacts?view=graph-rest-1.0&tabs=go
|
||||
func RestoreExchangeContact(
|
||||
// RestoreContact wraps api.Contacts().PostItem()
|
||||
func RestoreContact(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
service graph.Servicer,
|
||||
cli itemPoster[models.Contactable],
|
||||
cp control.CollisionPolicy,
|
||||
destination, user string,
|
||||
) (*details.ExchangeInfo, error) {
|
||||
@ -73,19 +77,9 @@ func RestoreExchangeContact(
|
||||
|
||||
ctx = clues.Add(ctx, "item_id", ptr.Val(contact.GetId()))
|
||||
|
||||
response, err := service.Client().
|
||||
Users().
|
||||
ByUserId(user).
|
||||
ContactFolders().
|
||||
ByContactFolderId(destination).
|
||||
Contacts().
|
||||
Post(ctx, contact, nil)
|
||||
_, err = cli.PostItem(ctx, user, destination, contact)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "uploading Contact")
|
||||
}
|
||||
|
||||
if response == nil {
|
||||
return nil, clues.New("nil response from post").WithClues(ctx)
|
||||
return nil, clues.Stack(err)
|
||||
}
|
||||
|
||||
info := api.ContactInfo(contact)
|
||||
@ -94,16 +88,13 @@ func RestoreExchangeContact(
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// RestoreExchangeEvent restores a contact to the @bits byte
|
||||
// representation of M365 event object.
|
||||
// @param destination is the M365 ID representing Calendar that will receive the event.
|
||||
// Returns an error if input byte array doesn't parse into models.Eventable object
|
||||
// or if an error occurs during sending data to M365 account.
|
||||
// Post details: https://docs.microsoft.com/en-us/graph/api/user-post-events?view=graph-rest-1.0&tabs=http
|
||||
func RestoreExchangeEvent(
|
||||
// RestoreEvent wraps api.Events().PostItem()
|
||||
func RestoreEvent(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
service graph.Servicer,
|
||||
itemCli itemPoster[models.Eventable],
|
||||
attachmentCli attachmentPoster,
|
||||
gs graph.Servicer,
|
||||
cp control.CollisionPolicy,
|
||||
destination, user string,
|
||||
errs *fault.Bus,
|
||||
@ -127,34 +118,24 @@ func RestoreExchangeEvent(
|
||||
transformedEvent.SetAttachments([]models.Attachmentable{})
|
||||
}
|
||||
|
||||
response, err := service.Client().
|
||||
Users().
|
||||
ByUserId(user).
|
||||
Calendars().
|
||||
ByCalendarId(destination).
|
||||
Events().
|
||||
Post(ctx, transformedEvent, nil)
|
||||
item, err := itemCli.PostItem(ctx, user, destination, event)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "uploading event")
|
||||
return nil, clues.Stack(err)
|
||||
}
|
||||
|
||||
if response == nil {
|
||||
return nil, clues.New("nil response from post").WithClues(ctx)
|
||||
}
|
||||
|
||||
uploader := &eventAttachmentUploader{
|
||||
calendarID: destination,
|
||||
userID: user,
|
||||
service: service,
|
||||
itemID: ptr.Val(response.GetId()),
|
||||
}
|
||||
|
||||
for _, attach := range attached {
|
||||
for _, a := range attached {
|
||||
if el.Failure() != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if err := uploadAttachment(ctx, uploader, attach); err != nil {
|
||||
err := uploadAttachment(
|
||||
ctx,
|
||||
attachmentCli,
|
||||
user,
|
||||
destination,
|
||||
ptr.Val(item.GetId()),
|
||||
a)
|
||||
if err != nil {
|
||||
el.AddRecoverable(err)
|
||||
}
|
||||
}
|
||||
@ -165,30 +146,27 @@ func RestoreExchangeEvent(
|
||||
return info, el.Failure()
|
||||
}
|
||||
|
||||
// RestoreMailMessage utility function to place an exchange.Mail
|
||||
// message into the user's M365 Exchange account.
|
||||
// @param bits - byte array representation of exchange.Message from Corso backstore
|
||||
// @param service - connector to M365 graph
|
||||
// @param cp - collision policy that directs restore workflow
|
||||
// @param destination - M365 Folder ID. Verified and sent by higher function. `copy` policy can use directly
|
||||
func RestoreMailMessage(
|
||||
// RestoreMessage wraps api.Mail().PostItem(), handling attachment creation along the way
|
||||
func RestoreMessage(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
service graph.Servicer,
|
||||
itemCli itemPoster[models.Messageable],
|
||||
attachmentCli attachmentPoster,
|
||||
gs graph.Servicer,
|
||||
cp control.CollisionPolicy,
|
||||
destination, user string,
|
||||
errs *fault.Bus,
|
||||
) (*details.ExchangeInfo, error) {
|
||||
// Creates messageable object from original bytes
|
||||
originalMessage, err := support.CreateMessageFromBytes(bits)
|
||||
msg, err := support.CreateMessageFromBytes(bits)
|
||||
if err != nil {
|
||||
return nil, clues.Wrap(err, "creating mail from bytes").WithClues(ctx)
|
||||
}
|
||||
|
||||
ctx = clues.Add(ctx, "item_id", ptr.Val(originalMessage.GetId()))
|
||||
ctx = clues.Add(ctx, "item_id", ptr.Val(msg.GetId()))
|
||||
|
||||
var (
|
||||
clone = support.ToMessage(originalMessage)
|
||||
clone = support.ToMessage(msg)
|
||||
valueID = MailRestorePropertyTag
|
||||
enableValue = RestoreCanonicalEnableValue
|
||||
)
|
||||
@ -225,80 +203,35 @@ func RestoreMailMessage(
|
||||
|
||||
clone.SetSingleValueExtendedProperties(svlep)
|
||||
|
||||
if err := SendMailToBackStore(ctx, service, user, destination, clone, errs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := api.MailInfo(clone, int64(len(bits)))
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// GetAttachmentBytes is a helper to retrieve the attachment content from a models.Attachmentable
|
||||
func GetAttachmentBytes(attachment models.Attachmentable) ([]byte, error) {
|
||||
bi, err := attachment.GetBackingStore().Get("contentBytes")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bts, ok := bi.([]byte)
|
||||
if !ok {
|
||||
return nil, clues.New(fmt.Sprintf("unexpected type for attachment content: %T", bi))
|
||||
}
|
||||
|
||||
return bts, nil
|
||||
}
|
||||
|
||||
// SendMailToBackStore function for transporting in-memory messageable item to M365 backstore
|
||||
// @param user string represents M365 ID of user within the tenant
|
||||
// @param destination represents M365 ID of a folder within the users's space
|
||||
// @param message is a models.Messageable interface from "github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
func SendMailToBackStore(
|
||||
ctx context.Context,
|
||||
service graph.Servicer,
|
||||
user, destination string,
|
||||
message models.Messageable,
|
||||
errs *fault.Bus,
|
||||
) error {
|
||||
attached := message.GetAttachments()
|
||||
attached := clone.GetAttachments()
|
||||
|
||||
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
|
||||
message.SetAttachments([]models.Attachmentable{})
|
||||
clone.SetAttachments([]models.Attachmentable{})
|
||||
|
||||
response, err := service.Client().
|
||||
Users().
|
||||
ByUserId(user).
|
||||
MailFolders().
|
||||
ByMailFolderId(destination).
|
||||
Messages().
|
||||
Post(ctx, message, nil)
|
||||
item, err := itemCli.PostItem(ctx, user, destination, clone)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "restoring mail")
|
||||
return nil, graph.Wrap(ctx, err, "restoring mail message")
|
||||
}
|
||||
|
||||
if response == nil {
|
||||
return clues.New("nil response from post").WithClues(ctx)
|
||||
}
|
||||
el := errs.Local()
|
||||
|
||||
var (
|
||||
el = errs.Local()
|
||||
id = ptr.Val(response.GetId())
|
||||
uploader = &mailAttachmentUploader{
|
||||
userID: user,
|
||||
folderID: destination,
|
||||
itemID: id,
|
||||
service: service,
|
||||
}
|
||||
)
|
||||
|
||||
for _, attachment := range attached {
|
||||
for _, a := range attached {
|
||||
if el.Failure() != nil {
|
||||
break
|
||||
return nil, el.Failure()
|
||||
}
|
||||
|
||||
if err := uploadAttachment(ctx, uploader, attachment); err != nil {
|
||||
if ptr.Val(attachment.GetOdataType()) == "#microsoft.graph.itemAttachment" {
|
||||
name := ptr.Val(attachment.GetName())
|
||||
err := uploadAttachment(
|
||||
ctx,
|
||||
attachmentCli,
|
||||
user,
|
||||
destination,
|
||||
ptr.Val(item.GetId()),
|
||||
a)
|
||||
if err != nil {
|
||||
// FIXME: I don't know why we're swallowing this error case.
|
||||
// It needs investigation: https://github.com/alcionai/corso/issues/3498
|
||||
if ptr.Val(a.GetOdataType()) == "#microsoft.graph.itemAttachment" {
|
||||
name := ptr.Val(a.GetName())
|
||||
|
||||
logger.CtxErr(ctx, err).
|
||||
With("attachment_name", name).
|
||||
@ -308,20 +241,18 @@ func SendMailToBackStore(
|
||||
}
|
||||
|
||||
el.AddRecoverable(clues.Wrap(err, "uploading mail attachment"))
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return el.Failure()
|
||||
return api.MailInfo(clone, int64(len(bits))), el.Failure()
|
||||
}
|
||||
|
||||
// RestoreExchangeDataCollections restores M365 objects in data.RestoreCollection to MSFT
|
||||
// RestoreCollections restores M365 objects in data.RestoreCollection to MSFT
|
||||
// store through GraphAPI.
|
||||
// @param dest: container destination to M365
|
||||
func RestoreExchangeDataCollections(
|
||||
func RestoreCollections(
|
||||
ctx context.Context,
|
||||
creds account.M365Config,
|
||||
ac api.Client,
|
||||
gs graph.Servicer,
|
||||
dest control.RestoreDestination,
|
||||
dcs []data.RestoreCollection,
|
||||
@ -365,7 +296,7 @@ func RestoreExchangeDataCollections(
|
||||
continue
|
||||
}
|
||||
|
||||
temp, canceled := restoreCollection(ctx, gs, dc, containerID, policy, deets, errs)
|
||||
temp, canceled := restoreCollection(ctx, ac, gs, dc, containerID, policy, deets, errs)
|
||||
|
||||
metrics = support.CombineMetrics(metrics, temp)
|
||||
|
||||
@ -387,6 +318,7 @@ func RestoreExchangeDataCollections(
|
||||
// restoreCollection handles restoration of an individual collection.
|
||||
func restoreCollection(
|
||||
ctx context.Context,
|
||||
ac api.Client,
|
||||
gs graph.Servicer,
|
||||
dc data.RestoreCollection,
|
||||
folderID string,
|
||||
@ -444,11 +376,12 @@ func restoreCollection(
|
||||
|
||||
byteArray := buf.Bytes()
|
||||
|
||||
info, err := RestoreExchangeObject(
|
||||
info, err := RestoreItem(
|
||||
ictx,
|
||||
byteArray,
|
||||
category,
|
||||
policy,
|
||||
ac,
|
||||
gs,
|
||||
folderID,
|
||||
user,
|
||||
|
||||
@ -7,6 +7,8 @@ import (
|
||||
"github.com/alcionai/corso/src/pkg/path"
|
||||
)
|
||||
|
||||
const AttachmentChunkSize = 4 * 1024 * 1024
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// item response AdditionalData
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -227,6 +227,8 @@ func (suite *RetryMWIntgSuite) TestRetryMiddleware_RetryRequest_resetBodyAfter50
|
||||
adpt, err := mockAdapter(suite.creds, mw)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
// no api package needed here, this is a mocked request that works
|
||||
// independent of the query.
|
||||
_, err = NewService(adpt).
|
||||
Client().
|
||||
Users().
|
||||
|
||||
@ -16,7 +16,6 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||
"github.com/alcionai/corso/src/internal/connector/exchange"
|
||||
"github.com/alcionai/corso/src/internal/connector/onedrive"
|
||||
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||
"github.com/alcionai/corso/src/internal/connector/support"
|
||||
@ -25,6 +24,7 @@ import (
|
||||
"github.com/alcionai/corso/src/pkg/fault"
|
||||
"github.com/alcionai/corso/src/pkg/path"
|
||||
"github.com/alcionai/corso/src/pkg/selectors"
|
||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||
)
|
||||
|
||||
func testElementsMatch[T any](
|
||||
@ -118,12 +118,12 @@ func attachmentEqual(
|
||||
expected models.Attachmentable,
|
||||
got models.Attachmentable,
|
||||
) bool {
|
||||
expectedData, err := exchange.GetAttachmentBytes(expected)
|
||||
expectedData, err := api.GetAttachmentContent(expected)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
gotData, err := exchange.GetAttachmentBytes(got)
|
||||
gotData, err := api.GetAttachmentContent(got)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"github.com/alcionai/corso/src/pkg/account"
|
||||
"github.com/alcionai/corso/src/pkg/control"
|
||||
"github.com/alcionai/corso/src/pkg/path"
|
||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -54,9 +55,9 @@ func mustGetDefaultDriveID(
|
||||
|
||||
switch backupService {
|
||||
case path.OneDriveService:
|
||||
d, err = service.Client().Users().ByUserId(resourceOwner).Drive().Get(ctx, nil)
|
||||
d, err = api.GetUsersDrive(ctx, service, resourceOwner)
|
||||
case path.SharePointService:
|
||||
d, err = service.Client().Sites().BySiteId(resourceOwner).Drive().Get(ctx, nil)
|
||||
d, err = api.GetSitesDefaultDrive(ctx, service, resourceOwner)
|
||||
default:
|
||||
assert.FailNowf(t, "unknown service type %s", backupService.String())
|
||||
}
|
||||
|
||||
@ -326,24 +326,3 @@ func GetAllFolders(
|
||||
|
||||
return res, el.Failure()
|
||||
}
|
||||
|
||||
// deletes require unique http clients
|
||||
// https://github.com/alcionai/corso/issues/2707
|
||||
func DeleteItem(
|
||||
ctx context.Context,
|
||||
gs graph.Servicer,
|
||||
driveID string,
|
||||
itemID string,
|
||||
) error {
|
||||
err := gs.Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
Delete(ctx, nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "deleting item").With("item_id", itemID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -337,7 +337,7 @@ func (suite *OneDriveIntgSuite) TestCreateGetDeleteFolder() {
|
||||
|
||||
// deletes require unique http clients
|
||||
// https://github.com/alcionai/corso/issues/2707
|
||||
err := DeleteItem(ictx, loadTestService(t), driveID, id)
|
||||
err := api.DeleteDriveItem(ictx, loadTestService(t), driveID, id)
|
||||
if err != nil {
|
||||
logger.CtxErr(ictx, err).Errorw("deleting folder")
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/drives"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||
@ -337,29 +336,20 @@ func sharePointItemInfo(di models.DriveItemable, itemSize int64) *details.ShareP
|
||||
// TODO: @vkamra verify if var session is the desired input
|
||||
func driveItemWriter(
|
||||
ctx context.Context,
|
||||
service graph.Servicer,
|
||||
gs graph.Servicer,
|
||||
driveID, itemID string,
|
||||
itemSize int64,
|
||||
) (io.Writer, error) {
|
||||
session := drives.NewItemItemsItemCreateUploadSessionPostRequestBody()
|
||||
ctx = clues.Add(ctx, "upload_item_id", itemID)
|
||||
|
||||
r, err := service.Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
CreateUploadSession().
|
||||
Post(ctx, session, nil)
|
||||
r, err := api.PostDriveItem(ctx, gs, driveID, itemID)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "creating item upload session")
|
||||
return nil, clues.Stack(err)
|
||||
}
|
||||
|
||||
logger.Ctx(ctx).Debug("created an upload session")
|
||||
iw := graph.NewLargeItemWriter(itemID, ptr.Val(r.GetUploadUrl()), itemSize)
|
||||
|
||||
url := ptr.Val(r.GetUploadUrl())
|
||||
|
||||
return graph.NewLargeItemWriter(itemID, url, itemSize), nil
|
||||
return iw, nil
|
||||
}
|
||||
|
||||
// constructWebURL helper function for recreating the webURL
|
||||
|
||||
@ -154,7 +154,7 @@ func (suite *ItemIntegrationSuite) TestItemWriter() {
|
||||
|
||||
srv := suite.service
|
||||
|
||||
root, err := srv.Client().Drives().ByDriveId(test.driveID).Root().Get(ctx, nil)
|
||||
root, err := api.GetDriveRoot(ctx, srv, test.driveID)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
newFolderName := tester.DefaultTestRestoreDestination("folder").ContainerName
|
||||
@ -233,7 +233,7 @@ func (suite *ItemIntegrationSuite) TestDriveGetFolder() {
|
||||
|
||||
srv := suite.service
|
||||
|
||||
root, err := srv.Client().Drives().ByDriveId(test.driveID).Root().Get(ctx, nil)
|
||||
root, err := api.GetDriveRoot(ctx, srv, test.driveID)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
// Lookup a folder that doesn't exist
|
||||
|
||||
@ -155,27 +155,20 @@ func UpdatePermissions(
|
||||
// https://github.com/alcionai/corso/issues/2707
|
||||
// this is bad citizenship, and could end up consuming a lot of
|
||||
// system resources if servicers leak client connections (sockets, etc).
|
||||
a, err := graph.CreateAdapter(creds.AzureTenantID, creds.AzureClientID, creds.AzureClientSecret)
|
||||
if err != nil {
|
||||
return graph.Wrap(ictx, err, "creating delete client")
|
||||
}
|
||||
|
||||
pid, ok := oldPermIDToNewID[p.ID]
|
||||
if !ok {
|
||||
return clues.New("no new permission id").WithClues(ctx)
|
||||
}
|
||||
|
||||
err = graph.NewService(a).
|
||||
Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
Permissions().
|
||||
ByPermissionId(pid).
|
||||
Delete(graph.ConsumeNTokens(ictx, graph.PermissionsLC), nil)
|
||||
err := api.DeleteDriveItemPermission(
|
||||
ictx,
|
||||
creds,
|
||||
driveID,
|
||||
itemID,
|
||||
pid)
|
||||
if err != nil {
|
||||
return graph.Wrap(ictx, err, "removing permissions")
|
||||
return clues.Stack(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
package sharepoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/microsoft/kiota-abstractions-go/serialization"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/sites"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||
)
|
||||
|
||||
// GetAllSitesForTenant makes a GraphQuery request retrieving all sites in the tenant.
|
||||
// Due to restrictions in filter capabilities for site queries, the returned iterable
|
||||
// will contain all personal sites for all users in the org.
|
||||
func GetAllSitesForTenant(ctx context.Context, gs graph.Servicer) (serialization.Parsable, error) {
|
||||
options := &sites.SitesRequestBuilderGetRequestConfiguration{
|
||||
QueryParameters: &sites.SitesRequestBuilderGetQueryParameters{
|
||||
Select: []string{"id", "name", "weburl"},
|
||||
},
|
||||
}
|
||||
|
||||
ss, err := gs.Client().Sites().Get(ctx, options)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "getting sites")
|
||||
}
|
||||
|
||||
return ss, nil
|
||||
}
|
||||
@ -983,14 +983,7 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
||||
body := users.NewItemMailFoldersItemMovePostRequestBody()
|
||||
body.SetDestinationId(ptr.To(to.containerID))
|
||||
|
||||
_, err := gc.Service.
|
||||
Client().
|
||||
Users().
|
||||
ByUserId(uidn.ID()).
|
||||
MailFolders().
|
||||
ByMailFolderId(from.containerID).
|
||||
Move().
|
||||
Post(ctx, body, nil)
|
||||
err := ac.Mail().MoveContainer(ctx, uidn.ID(), from.containerID, body)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
newLoc := expectDeets.MoveLocation(cat.String(), from.locRef, to.locRef)
|
||||
@ -1083,7 +1076,6 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
||||
name: "rename a folder",
|
||||
updateUserData: func(t *testing.T) {
|
||||
for category, d := range dataset {
|
||||
cli := gc.Service.Client().Users().ByUserId(uidn.ID())
|
||||
containerID := d.dests[container3].containerID
|
||||
newLoc := containerRename
|
||||
|
||||
@ -1103,34 +1095,28 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
||||
|
||||
switch category {
|
||||
case path.EmailCategory:
|
||||
cmf := cli.MailFolders().ByMailFolderId(containerID)
|
||||
|
||||
body, err := cmf.Get(ctx, nil)
|
||||
require.NoError(t, err, "getting mail folder", clues.ToCore(err))
|
||||
body, err := ac.Mail().GetFolder(ctx, uidn.ID(), containerID)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
body.SetDisplayName(&containerRename)
|
||||
_, err = cmf.Patch(ctx, body, nil)
|
||||
require.NoError(t, err, "updating mail folder name", clues.ToCore(err))
|
||||
err = ac.Mail().PatchFolder(ctx, uidn.ID(), containerID, body)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
case path.ContactsCategory:
|
||||
ccf := cli.ContactFolders().ByContactFolderId(containerID)
|
||||
|
||||
body, err := ccf.Get(ctx, nil)
|
||||
require.NoError(t, err, "getting contact folder", clues.ToCore(err))
|
||||
body, err := ac.Contacts().GetFolder(ctx, uidn.ID(), containerID)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
body.SetDisplayName(&containerRename)
|
||||
_, err = ccf.Patch(ctx, body, nil)
|
||||
require.NoError(t, err, "updating contact folder name", clues.ToCore(err))
|
||||
err = ac.Contacts().PatchFolder(ctx, uidn.ID(), containerID, body)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
case path.EventsCategory:
|
||||
cbi := cli.Calendars().ByCalendarId(containerID)
|
||||
|
||||
body, err := cbi.Get(ctx, nil)
|
||||
require.NoError(t, err, "getting calendar", clues.ToCore(err))
|
||||
body, err := ac.Events().GetCalendar(ctx, uidn.ID(), containerID)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
body.SetName(&containerRename)
|
||||
_, err = cbi.Patch(ctx, body, nil)
|
||||
require.NoError(t, err, "updating calendar name", clues.ToCore(err))
|
||||
err = ac.Events().PatchCalendar(ctx, uidn.ID(), containerID, body)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1146,16 +1132,15 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
||||
updateUserData: func(t *testing.T) {
|
||||
for category, d := range dataset {
|
||||
containerID := d.dests[container1].containerID
|
||||
cli := gc.Service.Client().Users().ByUserId(uidn.ID())
|
||||
|
||||
switch category {
|
||||
case path.EmailCategory:
|
||||
_, itemData := generateItemData(t, category, uidn.ID(), mailDBF)
|
||||
body, err := support.CreateMessageFromBytes(itemData)
|
||||
require.NoError(t, err, "transforming mail bytes to messageable", clues.ToCore(err))
|
||||
require.NoErrorf(t, err, "transforming mail bytes to messageable: %+v", clues.ToCore(err))
|
||||
|
||||
itm, err := cli.MailFolders().ByMailFolderId(containerID).Messages().Post(ctx, body, nil)
|
||||
require.NoError(t, err, "posting email item", clues.ToCore(err))
|
||||
itm, err := ac.Mail().PostItem(ctx, uidn.ID(), containerID, body)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
expectDeets.AddItem(
|
||||
category.String(),
|
||||
@ -1165,10 +1150,10 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
||||
case path.ContactsCategory:
|
||||
_, itemData := generateItemData(t, category, uidn.ID(), contactDBF)
|
||||
body, err := support.CreateContactFromBytes(itemData)
|
||||
require.NoError(t, err, "transforming contact bytes to contactable", clues.ToCore(err))
|
||||
require.NoErrorf(t, err, "transforming contact bytes to contactable: %+v", clues.ToCore(err))
|
||||
|
||||
itm, err := cli.ContactFolders().ByContactFolderId(containerID).Contacts().Post(ctx, body, nil)
|
||||
require.NoError(t, err, "posting contact item", clues.ToCore(err))
|
||||
itm, err := ac.Contacts().PostItem(ctx, uidn.ID(), containerID, body)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
expectDeets.AddItem(
|
||||
category.String(),
|
||||
@ -1178,10 +1163,10 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
||||
case path.EventsCategory:
|
||||
_, itemData := generateItemData(t, category, uidn.ID(), eventDBF)
|
||||
body, err := support.CreateEventFromBytes(itemData)
|
||||
require.NoError(t, err, "transforming event bytes to eventable", clues.ToCore(err))
|
||||
require.NoErrorf(t, err, "transforming event bytes to eventable: %+v", clues.ToCore(err))
|
||||
|
||||
itm, err := cli.Calendars().ByCalendarId(containerID).Events().Post(ctx, body, nil)
|
||||
require.NoError(t, err, "posting events item", clues.ToCore(err))
|
||||
itm, err := ac.Events().PostItem(ctx, uidn.ID(), containerID, body)
|
||||
require.NoError(t, err, clues.ToCore(err))
|
||||
|
||||
expectDeets.AddItem(
|
||||
category.String(),
|
||||
@ -1200,7 +1185,6 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
||||
updateUserData: func(t *testing.T) {
|
||||
for category, d := range dataset {
|
||||
containerID := d.dests[container1].containerID
|
||||
cli := gc.Service.Client().Users().ByUserId(uidn.ID())
|
||||
|
||||
switch category {
|
||||
case path.EmailCategory:
|
||||
@ -1208,7 +1192,7 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
||||
require.NoError(t, err, "getting message ids", clues.ToCore(err))
|
||||
require.NotEmpty(t, ids, "message ids in folder")
|
||||
|
||||
err = cli.Messages().ByMessageId(ids[0]).Delete(ctx, nil)
|
||||
err = ac.Mail().DeleteItem(ctx, uidn.ID(), ids[0])
|
||||
require.NoError(t, err, "deleting email item", clues.ToCore(err))
|
||||
|
||||
expectDeets.RemoveItem(
|
||||
@ -1221,7 +1205,7 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
||||
require.NoError(t, err, "getting contact ids", clues.ToCore(err))
|
||||
require.NotEmpty(t, ids, "contact ids in folder")
|
||||
|
||||
err = cli.Contacts().ByContactId(ids[0]).Delete(ctx, nil)
|
||||
err = ac.Contacts().DeleteItem(ctx, uidn.ID(), ids[0])
|
||||
require.NoError(t, err, "deleting contact item", clues.ToCore(err))
|
||||
|
||||
expectDeets.RemoveItem(
|
||||
@ -1234,7 +1218,7 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
||||
require.NoError(t, err, "getting event ids", clues.ToCore(err))
|
||||
require.NotEmpty(t, ids, "event ids in folder")
|
||||
|
||||
err = cli.Calendars().ByCalendarId(ids[0]).Delete(ctx, nil)
|
||||
err = ac.Events().DeleteItem(ctx, uidn.ID(), ids[0])
|
||||
require.NoError(t, err, "deleting calendar", clues.ToCore(err))
|
||||
|
||||
expectDeets.RemoveItem(
|
||||
@ -1666,14 +1650,12 @@ func runDriveIncrementalTest(
|
||||
{
|
||||
name: "update contents of a file",
|
||||
updateFiles: func(t *testing.T) {
|
||||
_, err := gc.Service.
|
||||
Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(ptr.Val(newFile.GetId())).
|
||||
Content().
|
||||
Put(ctx, []byte("new content"), nil)
|
||||
err := api.PutDriveItemContent(
|
||||
ctx,
|
||||
gc.Service,
|
||||
driveID,
|
||||
ptr.Val(newFile.GetId()),
|
||||
[]byte("new content"))
|
||||
require.NoErrorf(t, err, "updating file contents: %v", clues.ToCore(err))
|
||||
// no expectedDeets: neither file id nor location changed
|
||||
},
|
||||
@ -1692,13 +1674,12 @@ func runDriveIncrementalTest(
|
||||
parentRef.SetId(&container)
|
||||
driveItem.SetParentReference(parentRef)
|
||||
|
||||
_, err := gc.Service.
|
||||
Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(ptr.Val(newFile.GetId())).
|
||||
Patch(ctx, driveItem, nil)
|
||||
err := api.PatchDriveItem(
|
||||
ctx,
|
||||
gc.Service,
|
||||
driveID,
|
||||
ptr.Val(newFile.GetId()),
|
||||
driveItem)
|
||||
require.NoError(t, err, "renaming file %v", clues.ToCore(err))
|
||||
},
|
||||
itemsRead: 1, // .data file for newitem
|
||||
@ -1716,13 +1697,12 @@ func runDriveIncrementalTest(
|
||||
parentRef.SetId(&dest)
|
||||
driveItem.SetParentReference(parentRef)
|
||||
|
||||
_, err := gc.Service.
|
||||
Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(ptr.Val(newFile.GetId())).
|
||||
Patch(ctx, driveItem, nil)
|
||||
err := api.PatchDriveItem(
|
||||
ctx,
|
||||
gc.Service,
|
||||
driveID,
|
||||
ptr.Val(newFile.GetId()),
|
||||
driveItem)
|
||||
require.NoErrorf(t, err, "moving file between folders %v", clues.ToCore(err))
|
||||
|
||||
expectDeets.MoveItem(
|
||||
@ -1737,15 +1717,11 @@ func runDriveIncrementalTest(
|
||||
{
|
||||
name: "delete file",
|
||||
updateFiles: func(t *testing.T) {
|
||||
// deletes require unique http clients
|
||||
// https://github.com/alcionai/corso/issues/2707
|
||||
err = newDeleteServicer(t).
|
||||
Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(ptr.Val(newFile.GetId())).
|
||||
Delete(ctx, nil)
|
||||
err := api.DeleteDriveItem(
|
||||
ctx,
|
||||
newDeleteServicer(t),
|
||||
driveID,
|
||||
ptr.Val(newFile.GetId()))
|
||||
require.NoErrorf(t, err, "deleting file %v", clues.ToCore(err))
|
||||
|
||||
expectDeets.RemoveItem(driveID, makeLocRef(container2), ptr.Val(newFile.GetId()))
|
||||
@ -1765,13 +1741,12 @@ func runDriveIncrementalTest(
|
||||
parentRef.SetId(&parent)
|
||||
driveItem.SetParentReference(parentRef)
|
||||
|
||||
_, err := gc.Service.
|
||||
Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(child).
|
||||
Patch(ctx, driveItem, nil)
|
||||
err := api.PatchDriveItem(
|
||||
ctx,
|
||||
gc.Service,
|
||||
driveID,
|
||||
child,
|
||||
driveItem)
|
||||
require.NoError(t, err, "moving folder", clues.ToCore(err))
|
||||
|
||||
expectDeets.MoveLocation(
|
||||
@ -1794,13 +1769,12 @@ func runDriveIncrementalTest(
|
||||
parentRef.SetId(&parent)
|
||||
driveItem.SetParentReference(parentRef)
|
||||
|
||||
_, err := gc.Service.
|
||||
Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(child).
|
||||
Patch(ctx, driveItem, nil)
|
||||
err := api.PatchDriveItem(
|
||||
ctx,
|
||||
gc.Service,
|
||||
driveID,
|
||||
child,
|
||||
driveItem)
|
||||
require.NoError(t, err, "renaming folder", clues.ToCore(err))
|
||||
|
||||
containerIDs[containerRename] = containerIDs[container2]
|
||||
@ -1817,15 +1791,11 @@ func runDriveIncrementalTest(
|
||||
name: "delete a folder",
|
||||
updateFiles: func(t *testing.T) {
|
||||
container := containerIDs[containerRename]
|
||||
// deletes require unique http clients
|
||||
// https://github.com/alcionai/corso/issues/2707
|
||||
err = newDeleteServicer(t).
|
||||
Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(container).
|
||||
Delete(ctx, nil)
|
||||
err := api.DeleteDriveItem(
|
||||
ctx,
|
||||
newDeleteServicer(t),
|
||||
driveID,
|
||||
container)
|
||||
require.NoError(t, err, "deleting folder", clues.ToCore(err))
|
||||
|
||||
expectDeets.RemoveLocation(driveID, makeLocRef(container1, containerRename))
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||
@ -23,3 +25,27 @@ func HasAttachments(body models.ItemBodyable) bool {
|
||||
|
||||
return strings.Contains(ptr.Val(body.GetContent()), "src=\"cid:")
|
||||
}
|
||||
|
||||
func makeSessionAttachment(name string, size int64) *models.AttachmentItem {
|
||||
attItem := models.NewAttachmentItem()
|
||||
attType := models.FILE_ATTACHMENTTYPE
|
||||
attItem.SetAttachmentType(&attType)
|
||||
attItem.SetName(&name)
|
||||
attItem.SetSize(&size)
|
||||
|
||||
return attItem
|
||||
}
|
||||
|
||||
func GetAttachmentContent(attachment models.Attachmentable) ([]byte, error) {
|
||||
ibs, err := attachment.GetBackingStore().Get("contentBytes")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bs, ok := ibs.([]byte)
|
||||
if !ok {
|
||||
return nil, clues.New(fmt.Sprintf("unexpected type for attachment content: %T", ibs))
|
||||
}
|
||||
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ type Contacts struct {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// methods
|
||||
// containers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// CreateContactFolder makes a contact folder with the displayName of folderName.
|
||||
@ -72,40 +72,29 @@ func (c Contacts) DeleteContainer(
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetItem retrieves a Contactable item.
|
||||
func (c Contacts) GetItem(
|
||||
// prefer GetContainerByID where possible.
|
||||
// use this only in cases where the models.ContactFolderable
|
||||
// is required.
|
||||
func (c Contacts) GetFolder(
|
||||
ctx context.Context,
|
||||
user, itemID string,
|
||||
immutableIDs bool,
|
||||
_ *fault.Bus, // no attachments to iterate over, so this goes unused
|
||||
) (serialization.Parsable, *details.ExchangeInfo, error) {
|
||||
options := &users.ItemContactsContactItemRequestBuilderGetRequestConfiguration{
|
||||
Headers: newPreferHeaders(preferImmutableIDs(immutableIDs)),
|
||||
}
|
||||
|
||||
cont, err := c.Stable.Client().Users().ByUserId(user).Contacts().ByContactId(itemID).Get(ctx, options)
|
||||
userID, containerID string,
|
||||
) (models.ContactFolderable, error) {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return nil, nil, graph.Stack(ctx, err)
|
||||
return nil, graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
return cont, ContactInfo(cont), nil
|
||||
}
|
||||
|
||||
func (c Contacts) GetContainerByID(
|
||||
ctx context.Context,
|
||||
userID, dirID string,
|
||||
) (graph.Container, error) {
|
||||
config := &users.ItemContactFoldersContactFolderItemRequestBuilderGetRequestConfiguration{
|
||||
QueryParameters: &users.ItemContactFoldersContactFolderItemRequestBuilderGetQueryParameters{
|
||||
Select: idAnd(displayName, parentFolderID),
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := c.Stable.Client().
|
||||
resp, err := service.Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
ContactFolders().
|
||||
ByContactFolderId(dirID).
|
||||
ByContactFolderId(containerID).
|
||||
Get(ctx, config)
|
||||
if err != nil {
|
||||
return nil, graph.Stack(ctx, err)
|
||||
@ -114,6 +103,41 @@ func (c Contacts) GetContainerByID(
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// interface-compliant wrapper of GetFolder
|
||||
func (c Contacts) GetContainerByID(
|
||||
ctx context.Context,
|
||||
userID, dirID string,
|
||||
) (graph.Container, error) {
|
||||
return c.GetFolder(ctx, userID, dirID)
|
||||
}
|
||||
|
||||
func (c Contacts) PatchFolder(
|
||||
ctx context.Context,
|
||||
userID, containerID string,
|
||||
body models.ContactFolderable,
|
||||
) error {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
_, err = service.Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
ContactFolders().
|
||||
ByContactFolderId(containerID).
|
||||
Patch(ctx, body, nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "patching contact folder")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// container pager
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// EnumerateContainers iterates through all of the users current
|
||||
// contacts folders, converting each to a graph.CacheFolder, and calling
|
||||
// fn(cf) on each one.
|
||||
@ -187,6 +211,77 @@ func (c Contacts) EnumerateContainers(
|
||||
return el.Failure()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// items
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// GetItem retrieves a Contactable item.
|
||||
func (c Contacts) GetItem(
|
||||
ctx context.Context,
|
||||
user, itemID string,
|
||||
immutableIDs bool,
|
||||
_ *fault.Bus, // no attachments to iterate over, so this goes unused
|
||||
) (serialization.Parsable, *details.ExchangeInfo, error) {
|
||||
options := &users.ItemContactsContactItemRequestBuilderGetRequestConfiguration{
|
||||
Headers: newPreferHeaders(preferImmutableIDs(immutableIDs)),
|
||||
}
|
||||
|
||||
cont, err := c.Stable.Client().Users().ByUserId(user).Contacts().ByContactId(itemID).Get(ctx, options)
|
||||
if err != nil {
|
||||
return nil, nil, graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
return cont, ContactInfo(cont), nil
|
||||
}
|
||||
|
||||
func (c Contacts) PostItem(
|
||||
ctx context.Context,
|
||||
userID, containerID string,
|
||||
body models.Contactable,
|
||||
) (models.Contactable, error) {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return nil, graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
itm, err := service.Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
ContactFolders().
|
||||
ByContactFolderId(containerID).
|
||||
Contacts().
|
||||
Post(ctx, body, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "creating contact")
|
||||
}
|
||||
|
||||
return itm, nil
|
||||
}
|
||||
|
||||
func (c Contacts) DeleteItem(
|
||||
ctx context.Context,
|
||||
userID, itemID string,
|
||||
) error {
|
||||
// deletes require unique http clients
|
||||
// https://github.com/alcionai/corso/issues/2707
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
err = service.Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
Contacts().
|
||||
ByContactId(itemID).
|
||||
Delete(ctx, nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "deleting contact")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// item pager
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -9,46 +9,12 @@ import (
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||
"github.com/alcionai/corso/src/pkg/account"
|
||||
)
|
||||
|
||||
// generic drive item getter
|
||||
func GetDriveItem(
|
||||
ctx context.Context,
|
||||
srv graph.Servicer,
|
||||
driveID, itemID string,
|
||||
) (models.DriveItemable, error) {
|
||||
di, err := srv.Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
Get(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "getting item")
|
||||
}
|
||||
|
||||
return di, nil
|
||||
}
|
||||
|
||||
func GetItemPermission(
|
||||
ctx context.Context,
|
||||
service graph.Servicer,
|
||||
driveID, itemID string,
|
||||
) (models.PermissionCollectionResponseable, error) {
|
||||
perm, err := service.
|
||||
Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
Permissions().
|
||||
Get(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "getting item metadata").With("item_id", itemID)
|
||||
}
|
||||
|
||||
return perm, nil
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// Drives
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func GetUsersDrive(
|
||||
ctx context.Context,
|
||||
@ -89,7 +55,11 @@ func GetDriveRoot(
|
||||
srv graph.Servicer,
|
||||
driveID string,
|
||||
) (models.DriveItemable, error) {
|
||||
root, err := srv.Client().Drives().ByDriveId(driveID).Root().Get(ctx, nil)
|
||||
root, err := srv.Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Root().
|
||||
Get(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "getting drive root")
|
||||
}
|
||||
@ -97,6 +67,109 @@ func GetDriveRoot(
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Drive Items
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// generic drive item getter
|
||||
func GetDriveItem(
|
||||
ctx context.Context,
|
||||
srv graph.Servicer,
|
||||
driveID, itemID string,
|
||||
) (models.DriveItemable, error) {
|
||||
di, err := srv.Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
Get(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "getting item")
|
||||
}
|
||||
|
||||
return di, nil
|
||||
}
|
||||
|
||||
func PostDriveItem(
|
||||
ctx context.Context,
|
||||
srv graph.Servicer,
|
||||
driveID, itemID string,
|
||||
) (models.UploadSessionable, error) {
|
||||
session := drives.NewItemItemsItemCreateUploadSessionPostRequestBody()
|
||||
|
||||
r, err := srv.Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
CreateUploadSession().
|
||||
Post(ctx, session, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "uploading drive item")
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func PatchDriveItem(
|
||||
ctx context.Context,
|
||||
srv graph.Servicer,
|
||||
driveID, itemID string,
|
||||
item models.DriveItemable,
|
||||
) error {
|
||||
_, err := srv.Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
Patch(ctx, item, nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "patching drive item")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PutDriveItemContent(
|
||||
ctx context.Context,
|
||||
srv graph.Servicer,
|
||||
driveID, itemID string,
|
||||
content []byte,
|
||||
) error {
|
||||
_, err := srv.Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
Content().
|
||||
Put(ctx, content, nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "uploading drive item content")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deletes require unique http clients
|
||||
// https://github.com/alcionai/corso/issues/2707
|
||||
func DeleteDriveItem(
|
||||
ctx context.Context,
|
||||
gs graph.Servicer,
|
||||
driveID, itemID string,
|
||||
) error {
|
||||
err := gs.Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
Delete(ctx, nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "deleting item").With("item_id", itemID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const itemByPathRawURLFmt = "https://graph.microsoft.com/v1.0/drives/%s/items/%s:/%s"
|
||||
|
||||
var ErrFolderNotFound = clues.New("folder not found")
|
||||
@ -104,7 +177,7 @@ var ErrFolderNotFound = clues.New("folder not found")
|
||||
// GetFolderByName will lookup the specified folder by name within the parentFolderID folder.
|
||||
func GetFolderByName(
|
||||
ctx context.Context,
|
||||
service graph.Servicer,
|
||||
srv graph.Servicer,
|
||||
driveID, parentFolderID, folder string,
|
||||
) (models.DriveItemable, error) {
|
||||
// The `Children().Get()` API doesn't yet support $filter, so using that to find a folder
|
||||
@ -113,7 +186,7 @@ func GetFolderByName(
|
||||
// https://learn.microsoft.com/en-us/graph/onedrive-addressing-driveitems#path-based-addressing
|
||||
// - which allows us to lookup an item by its path relative to the parent ID
|
||||
rawURL := fmt.Sprintf(itemByPathRawURLFmt, driveID, parentFolderID, folder)
|
||||
builder := drives.NewItemItemsDriveItemItemRequestBuilder(rawURL, service.Adapter())
|
||||
builder := drives.NewItemItemsDriveItemItemRequestBuilder(rawURL, srv.Adapter())
|
||||
|
||||
foundItem, err := builder.Get(ctx, nil)
|
||||
if err != nil {
|
||||
@ -132,6 +205,30 @@ func GetFolderByName(
|
||||
return foundItem, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Permissions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func GetItemPermission(
|
||||
ctx context.Context,
|
||||
service graph.Servicer,
|
||||
driveID, itemID string,
|
||||
) (models.PermissionCollectionResponseable, error) {
|
||||
perm, err := service.
|
||||
Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
Permissions().
|
||||
Get(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "getting item metadata").With("item_id", itemID)
|
||||
}
|
||||
|
||||
return perm, nil
|
||||
}
|
||||
|
||||
func PostItemPermissionUpdate(
|
||||
ctx context.Context,
|
||||
service graph.Servicer,
|
||||
@ -153,3 +250,29 @@ func PostItemPermissionUpdate(
|
||||
|
||||
return itm, nil
|
||||
}
|
||||
|
||||
func DeleteDriveItemPermission(
|
||||
ctx context.Context,
|
||||
creds account.M365Config,
|
||||
driveID, itemID, permissionID string,
|
||||
) error {
|
||||
a, err := graph.CreateAdapter(creds.AzureTenantID, creds.AzureClientID, creds.AzureClientSecret)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "creating adapter to delete item permission")
|
||||
}
|
||||
|
||||
err = graph.NewService(a).
|
||||
Client().
|
||||
Drives().
|
||||
ByDriveId(driveID).
|
||||
Items().
|
||||
ByDriveItemId(itemID).
|
||||
Permissions().
|
||||
ByPermissionId(permissionID).
|
||||
Delete(graph.ConsumeNTokens(ctx, graph.PermissionsLC), nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "deleting drive item permission")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
@ -33,7 +35,7 @@ type Events struct {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// methods
|
||||
// containers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// CreateCalendar makes an event Calendar with the name in the user's M365 exchange account
|
||||
@ -74,10 +76,13 @@ func (c Events) DeleteContainer(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Events) GetContainerByID(
|
||||
// prefer GetContainerByID where possible.
|
||||
// use this only in cases where the models.Calendarable
|
||||
// is required.
|
||||
func (c Events) GetCalendar(
|
||||
ctx context.Context,
|
||||
userID, containerID string,
|
||||
) (graph.Container, error) {
|
||||
) (models.Calendarable, error) {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return nil, graph.Stack(ctx, err)
|
||||
@ -89,14 +94,27 @@ func (c Events) GetContainerByID(
|
||||
},
|
||||
}
|
||||
|
||||
cal, err := service.Client().
|
||||
resp, err := service.Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
Calendars().
|
||||
ByCalendarId(containerID).
|
||||
Get(ctx, config)
|
||||
if err != nil {
|
||||
return nil, graph.Stack(ctx, err).WithClues(ctx)
|
||||
return nil, graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// interface-compliant wrapper of GetCalendar
|
||||
func (c Events) GetContainerByID(
|
||||
ctx context.Context,
|
||||
userID, dirID string,
|
||||
) (graph.Container, error) {
|
||||
cal, err := c.GetCalendar(ctx, userID, dirID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return graph.CalendarDisplayable{Calendarable: cal}, nil
|
||||
@ -141,57 +159,33 @@ func (c Events) GetContainerByName(
|
||||
return cal, nil
|
||||
}
|
||||
|
||||
// GetItem retrieves an Eventable item.
|
||||
func (c Events) GetItem(
|
||||
func (c Events) PatchCalendar(
|
||||
ctx context.Context,
|
||||
user, itemID string,
|
||||
immutableIDs bool,
|
||||
errs *fault.Bus,
|
||||
) (serialization.Parsable, *details.ExchangeInfo, error) {
|
||||
var (
|
||||
err error
|
||||
event models.Eventable
|
||||
config = &users.ItemEventsEventItemRequestBuilderGetRequestConfiguration{
|
||||
Headers: newPreferHeaders(preferImmutableIDs(immutableIDs)),
|
||||
}
|
||||
)
|
||||
|
||||
event, err = c.Stable.Client().
|
||||
Users().
|
||||
ByUserId(user).
|
||||
Events().
|
||||
ByEventId(itemID).
|
||||
Get(ctx, config)
|
||||
userID, containerID string,
|
||||
body models.Calendarable,
|
||||
) error {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return nil, nil, graph.Stack(ctx, err)
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
if ptr.Val(event.GetHasAttachments()) || HasAttachments(event.GetBody()) {
|
||||
config := &users.ItemEventsItemAttachmentsRequestBuilderGetRequestConfiguration{
|
||||
QueryParameters: &users.ItemEventsItemAttachmentsRequestBuilderGetQueryParameters{
|
||||
Expand: []string{"microsoft.graph.itemattachment/item"},
|
||||
},
|
||||
Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)),
|
||||
}
|
||||
|
||||
attached, err := c.LargeItem.
|
||||
Client().
|
||||
_, err = service.Client().
|
||||
Users().
|
||||
ByUserId(user).
|
||||
Events().
|
||||
ByEventId(itemID).
|
||||
Attachments().
|
||||
Get(ctx, config)
|
||||
ByUserId(userID).
|
||||
Calendars().
|
||||
ByCalendarId(containerID).
|
||||
Patch(ctx, body, nil)
|
||||
if err != nil {
|
||||
return nil, nil, graph.Wrap(ctx, err, "event attachment download")
|
||||
return graph.Wrap(ctx, err, "patching event calendar")
|
||||
}
|
||||
|
||||
event.SetAttachments(attached.GetValue())
|
||||
}
|
||||
|
||||
return event, EventInfo(event), nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// container pager
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// EnumerateContainers iterates through all of the users current
|
||||
// calendars, converting each to a graph.CacheFolder, and
|
||||
// calling fn(cf) on each one.
|
||||
@ -272,6 +266,176 @@ const (
|
||||
eventBetaDeltaURLTemplate = "https://graph.microsoft.com/beta/users/%s/calendars/%s/events/delta"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// items
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// GetItem retrieves an Eventable item.
|
||||
func (c Events) GetItem(
|
||||
ctx context.Context,
|
||||
user, itemID string,
|
||||
immutableIDs bool,
|
||||
errs *fault.Bus,
|
||||
) (serialization.Parsable, *details.ExchangeInfo, error) {
|
||||
var (
|
||||
err error
|
||||
event models.Eventable
|
||||
config = &users.ItemEventsEventItemRequestBuilderGetRequestConfiguration{
|
||||
Headers: newPreferHeaders(preferImmutableIDs(immutableIDs)),
|
||||
}
|
||||
)
|
||||
|
||||
event, err = c.Stable.Client().
|
||||
Users().
|
||||
ByUserId(user).
|
||||
Events().
|
||||
ByEventId(itemID).
|
||||
Get(ctx, config)
|
||||
if err != nil {
|
||||
return nil, nil, graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
if ptr.Val(event.GetHasAttachments()) || HasAttachments(event.GetBody()) {
|
||||
config := &users.ItemEventsItemAttachmentsRequestBuilderGetRequestConfiguration{
|
||||
QueryParameters: &users.ItemEventsItemAttachmentsRequestBuilderGetQueryParameters{
|
||||
Expand: []string{"microsoft.graph.itemattachment/item"},
|
||||
},
|
||||
Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)),
|
||||
}
|
||||
|
||||
attached, err := c.LargeItem.
|
||||
Client().
|
||||
Users().
|
||||
ByUserId(user).
|
||||
Events().
|
||||
ByEventId(itemID).
|
||||
Attachments().
|
||||
Get(ctx, config)
|
||||
if err != nil {
|
||||
return nil, nil, graph.Wrap(ctx, err, "event attachment download")
|
||||
}
|
||||
|
||||
event.SetAttachments(attached.GetValue())
|
||||
}
|
||||
|
||||
return event, EventInfo(event), nil
|
||||
}
|
||||
|
||||
func (c Events) PostItem(
|
||||
ctx context.Context,
|
||||
userID, containerID string,
|
||||
body models.Eventable,
|
||||
) (models.Eventable, error) {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return nil, graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
itm, err := service.Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
Calendars().
|
||||
ByCalendarId(containerID).
|
||||
Events().
|
||||
Post(ctx, body, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "creating calendar event")
|
||||
}
|
||||
|
||||
return itm, nil
|
||||
}
|
||||
|
||||
func (c Events) DeleteItem(
|
||||
ctx context.Context,
|
||||
userID, itemID string,
|
||||
) error {
|
||||
// deletes require unique http clients
|
||||
// https://github.com/alcionai/corso/issues/2707
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
err = service.Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
Events().
|
||||
ByEventId(itemID).
|
||||
Delete(ctx, nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "deleting calendar event")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Events) PostSmallAttachment(
|
||||
ctx context.Context,
|
||||
userID, containerID, parentItemID string,
|
||||
body models.Attachmentable,
|
||||
) error {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
_, err = service.Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
Calendars().
|
||||
ByCalendarId(containerID).
|
||||
Events().
|
||||
ByEventId(parentItemID).
|
||||
Attachments().
|
||||
Post(ctx, body, nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "uploading small event attachment")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Events) PostLargeAttachment(
|
||||
ctx context.Context,
|
||||
userID, containerID, parentItemID, name string,
|
||||
size int64,
|
||||
body models.Attachmentable,
|
||||
) (models.UploadSessionable, error) {
|
||||
bs, err := GetAttachmentContent(body)
|
||||
if err != nil {
|
||||
return nil, clues.Wrap(err, "serializing attachment content").WithClues(ctx)
|
||||
}
|
||||
|
||||
session := users.NewItemCalendarEventsItemAttachmentsCreateUploadSessionPostRequestBody()
|
||||
session.SetAttachmentItem(makeSessionAttachment(name, size))
|
||||
|
||||
us, err := c.LargeItem.
|
||||
Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
Calendars().
|
||||
ByCalendarId(containerID).
|
||||
Events().
|
||||
ByEventId(parentItemID).
|
||||
Attachments().
|
||||
CreateUploadSession().
|
||||
Post(ctx, session, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "uploading large event attachment")
|
||||
}
|
||||
|
||||
url := ptr.Val(us.GetUploadUrl())
|
||||
w := graph.NewLargeItemWriter(parentItemID, url, size)
|
||||
copyBuffer := make([]byte, graph.AttachmentChunkSize)
|
||||
|
||||
_, err = io.CopyBuffer(w, bytes.NewReader(bs), copyBuffer)
|
||||
if err != nil {
|
||||
return nil, clues.Wrap(err, "buffering large attachment content").WithClues(ctx)
|
||||
}
|
||||
|
||||
return us, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// item pager
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/microsoft/kiota-abstractions-go/serialization"
|
||||
@ -36,7 +38,7 @@ type Mail struct {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// methods
|
||||
// containers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// CreateMailFolder makes a mail folder iff a folder of the same name does not exist
|
||||
@ -113,10 +115,13 @@ func (c Mail) DeleteContainer(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Mail) GetContainerByID(
|
||||
// prefer GetContainerByID where possible.
|
||||
// use this only in cases where the models.MailFolderable
|
||||
// is required.
|
||||
func (c Mail) GetFolder(
|
||||
ctx context.Context,
|
||||
userID, dirID string,
|
||||
) (graph.Container, error) {
|
||||
userID, containerID string,
|
||||
) (models.MailFolderable, error) {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return nil, graph.Stack(ctx, err)
|
||||
@ -132,7 +137,7 @@ func (c Mail) GetContainerByID(
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
MailFolders().
|
||||
ByMailFolderId(dirID).
|
||||
ByMailFolderId(containerID).
|
||||
Get(ctx, config)
|
||||
if err != nil {
|
||||
return nil, graph.Stack(ctx, err)
|
||||
@ -141,6 +146,175 @@ func (c Mail) GetContainerByID(
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// interface-compliant wrapper of GetFolder
|
||||
func (c Mail) GetContainerByID(
|
||||
ctx context.Context,
|
||||
userID, dirID string,
|
||||
) (graph.Container, error) {
|
||||
return c.GetFolder(ctx, userID, dirID)
|
||||
}
|
||||
|
||||
func (c Mail) MoveContainer(
|
||||
ctx context.Context,
|
||||
userID, containerID string,
|
||||
body users.ItemMailFoldersItemMovePostRequestBodyable,
|
||||
) error {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
_, err = service.
|
||||
Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
MailFolders().
|
||||
ByMailFolderId(containerID).
|
||||
Move().
|
||||
Post(ctx, body, nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "moving mail folder")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Mail) PatchFolder(
|
||||
ctx context.Context,
|
||||
userID, containerID string,
|
||||
body models.MailFolderable,
|
||||
) error {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
_, err = service.Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
MailFolders().
|
||||
ByMailFolderId(containerID).
|
||||
Patch(ctx, body, nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "patching mail folder")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// container pager
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type mailFolderPager struct {
|
||||
service graph.Servicer
|
||||
builder *users.ItemMailFoldersRequestBuilder
|
||||
}
|
||||
|
||||
func NewMailFolderPager(service graph.Servicer, user string) mailFolderPager {
|
||||
// v1.0 non delta /mailFolders endpoint does not return any of the nested folders
|
||||
rawURL := fmt.Sprintf(mailFoldersBetaURLTemplate, user)
|
||||
builder := users.NewItemMailFoldersRequestBuilder(rawURL, service.Adapter())
|
||||
|
||||
return mailFolderPager{service, builder}
|
||||
}
|
||||
|
||||
func (p *mailFolderPager) getPage(ctx context.Context) (PageLinker, error) {
|
||||
page, err := p.builder.Get(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (p *mailFolderPager) setNext(nextLink string) {
|
||||
p.builder = users.NewItemMailFoldersRequestBuilder(nextLink, p.service.Adapter())
|
||||
}
|
||||
|
||||
func (p *mailFolderPager) valuesIn(pl PageLinker) ([]models.MailFolderable, error) {
|
||||
// Ideally this should be `users.ItemMailFoldersResponseable`, but
|
||||
// that is not a thing as stable returns different result
|
||||
page, ok := pl.(models.MailFolderCollectionResponseable)
|
||||
if !ok {
|
||||
return nil, clues.New("converting to ItemMailFoldersResponseable")
|
||||
}
|
||||
|
||||
return page.GetValue(), nil
|
||||
}
|
||||
|
||||
// EnumerateContainers iterates through all of the users current
|
||||
// mail folders, converting each to a graph.CacheFolder, and calling
|
||||
// fn(cf) on each one.
|
||||
// Folder hierarchy is represented in its current state, and does
|
||||
// not contain historical data.
|
||||
func (c Mail) EnumerateContainers(
|
||||
ctx context.Context,
|
||||
userID, baseDirID string,
|
||||
fn func(graph.CachedContainer) error,
|
||||
errs *fault.Bus,
|
||||
) error {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
el := errs.Local()
|
||||
|
||||
pgr := NewMailFolderPager(service, userID)
|
||||
|
||||
for {
|
||||
if el.Failure() != nil {
|
||||
break
|
||||
}
|
||||
|
||||
page, err := pgr.getPage(ctx)
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
resp, err := pgr.valuesIn(page)
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
for _, fold := range resp {
|
||||
if el.Failure() != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if err := graph.CheckIDNameAndParentFolderID(fold); err != nil {
|
||||
errs.AddRecoverable(graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
|
||||
continue
|
||||
}
|
||||
|
||||
fctx := clues.Add(
|
||||
ctx,
|
||||
"container_id", ptr.Val(fold.GetId()),
|
||||
"container_name", ptr.Val(fold.GetDisplayName()))
|
||||
|
||||
temp := graph.NewCacheFolder(fold, nil, nil)
|
||||
if err := fn(&temp); err != nil {
|
||||
errs.AddRecoverable(graph.Stack(fctx, err).Label(fault.LabelForceNoBackupCreation))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
link, ok := ptr.ValOK(page.GetOdataNextLink())
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
pgr.setNext(link)
|
||||
}
|
||||
|
||||
return el.Failure()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// items
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// GetItem retrieves a Messageable item. If the item contains an attachment, that
|
||||
// attachment is also downloaded.
|
||||
func (c Mail) GetItem(
|
||||
@ -265,109 +439,126 @@ func (c Mail) GetItem(
|
||||
return mail, MailInfo(mail, size), nil
|
||||
}
|
||||
|
||||
type mailFolderPager struct {
|
||||
service graph.Servicer
|
||||
builder *users.ItemMailFoldersRequestBuilder
|
||||
}
|
||||
|
||||
func NewMailFolderPager(service graph.Servicer, user string) mailFolderPager {
|
||||
// v1.0 non delta /mailFolders endpoint does not return any of the nested folders
|
||||
rawURL := fmt.Sprintf(mailFoldersBetaURLTemplate, user)
|
||||
builder := users.NewItemMailFoldersRequestBuilder(rawURL, service.Adapter())
|
||||
|
||||
return mailFolderPager{service, builder}
|
||||
}
|
||||
|
||||
func (p *mailFolderPager) getPage(ctx context.Context) (PageLinker, error) {
|
||||
page, err := p.builder.Get(ctx, nil)
|
||||
func (c Mail) PostItem(
|
||||
ctx context.Context,
|
||||
userID, containerID string,
|
||||
body models.Messageable,
|
||||
) (models.Messageable, error) {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return nil, graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (p *mailFolderPager) setNext(nextLink string) {
|
||||
p.builder = users.NewItemMailFoldersRequestBuilder(nextLink, p.service.Adapter())
|
||||
}
|
||||
|
||||
func (p *mailFolderPager) valuesIn(pl PageLinker) ([]models.MailFolderable, error) {
|
||||
// Ideally this should be `users.ItemMailFoldersResponseable`, but
|
||||
// that is not a thing as stable returns different result
|
||||
page, ok := pl.(models.MailFolderCollectionResponseable)
|
||||
if !ok {
|
||||
return nil, clues.New("converting to ItemMailFoldersResponseable")
|
||||
itm, err := service.
|
||||
Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
MailFolders().
|
||||
ByMailFolderId(containerID).
|
||||
Messages().
|
||||
Post(ctx, body, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "creating mail message")
|
||||
}
|
||||
|
||||
return page.GetValue(), nil
|
||||
if itm == nil {
|
||||
return nil, clues.New("nil response mail message creation").WithClues(ctx)
|
||||
}
|
||||
|
||||
return itm, nil
|
||||
}
|
||||
|
||||
// EnumerateContainers iterates through all of the users current
|
||||
// mail folders, converting each to a graph.CacheFolder, and calling
|
||||
// fn(cf) on each one.
|
||||
// Folder hierarchy is represented in its current state, and does
|
||||
// not contain historical data.
|
||||
func (c Mail) EnumerateContainers(
|
||||
func (c Mail) DeleteItem(
|
||||
ctx context.Context,
|
||||
userID, baseDirID string,
|
||||
fn func(graph.CachedContainer) error,
|
||||
errs *fault.Bus,
|
||||
userID, itemID string,
|
||||
) error {
|
||||
// deletes require unique http clients
|
||||
// https://github.com/alcionai/corso/issues/2707
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
err = service.
|
||||
Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
Messages().
|
||||
ByMessageId(itemID).
|
||||
Delete(ctx, nil)
|
||||
if err != nil {
|
||||
return graph.Wrap(ctx, err, "deleting mail message")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Mail) PostSmallAttachment(
|
||||
ctx context.Context,
|
||||
userID, containerID, parentItemID string,
|
||||
body models.Attachmentable,
|
||||
) error {
|
||||
service, err := c.Service()
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
}
|
||||
|
||||
el := errs.Local()
|
||||
|
||||
pgr := NewMailFolderPager(service, userID)
|
||||
|
||||
for {
|
||||
if el.Failure() != nil {
|
||||
break
|
||||
}
|
||||
|
||||
page, err := pgr.getPage(ctx)
|
||||
_, err = service.
|
||||
Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
MailFolders().
|
||||
ByMailFolderId(containerID).
|
||||
Messages().
|
||||
ByMessageId(parentItemID).
|
||||
Attachments().
|
||||
Post(ctx, body, nil)
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
return graph.Wrap(ctx, err, "uploading small mail attachment")
|
||||
}
|
||||
|
||||
resp, err := pgr.valuesIn(page)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Mail) PostLargeAttachment(
|
||||
ctx context.Context,
|
||||
userID, containerID, parentItemID, name string,
|
||||
size int64,
|
||||
body models.Attachmentable,
|
||||
) (models.UploadSessionable, error) {
|
||||
bs, err := GetAttachmentContent(body)
|
||||
if err != nil {
|
||||
return graph.Stack(ctx, err)
|
||||
return nil, clues.Wrap(err, "serializing attachment content").WithClues(ctx)
|
||||
}
|
||||
|
||||
for _, fold := range resp {
|
||||
if el.Failure() != nil {
|
||||
break
|
||||
session := users.NewItemMailFoldersItemMessagesItemAttachmentsCreateUploadSessionPostRequestBody()
|
||||
session.SetAttachmentItem(makeSessionAttachment(name, size))
|
||||
|
||||
us, err := c.LargeItem.
|
||||
Client().
|
||||
Users().
|
||||
ByUserId(userID).
|
||||
MailFolders().
|
||||
ByMailFolderId(containerID).
|
||||
Messages().
|
||||
ByMessageId(parentItemID).
|
||||
Attachments().
|
||||
CreateUploadSession().
|
||||
Post(ctx, session, nil)
|
||||
if err != nil {
|
||||
return nil, graph.Wrap(ctx, err, "uploading large mail attachment")
|
||||
}
|
||||
|
||||
if err := graph.CheckIDNameAndParentFolderID(fold); err != nil {
|
||||
errs.AddRecoverable(graph.Stack(ctx, err).Label(fault.LabelForceNoBackupCreation))
|
||||
continue
|
||||
url := ptr.Val(us.GetUploadUrl())
|
||||
w := graph.NewLargeItemWriter(parentItemID, url, size)
|
||||
copyBuffer := make([]byte, graph.AttachmentChunkSize)
|
||||
|
||||
_, err = io.CopyBuffer(w, bytes.NewReader(bs), copyBuffer)
|
||||
if err != nil {
|
||||
return nil, clues.Wrap(err, "buffering large attachment content").WithClues(ctx)
|
||||
}
|
||||
|
||||
fctx := clues.Add(
|
||||
ctx,
|
||||
"container_id", ptr.Val(fold.GetId()),
|
||||
"container_name", ptr.Val(fold.GetDisplayName()))
|
||||
|
||||
temp := graph.NewCacheFolder(fold, nil, nil)
|
||||
if err := fn(&temp); err != nil {
|
||||
errs.AddRecoverable(graph.Stack(fctx, err).Label(fault.LabelForceNoBackupCreation))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
link, ok := ptr.ValOK(page.GetOdataNextLink())
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
pgr.setNext(link)
|
||||
}
|
||||
|
||||
return el.Failure()
|
||||
return us, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user