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/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365"
|
"github.com/alcionai/corso/src/pkg/services/m365"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var purgeCmd = &cobra.Command{
|
var purgeCmd = &cobra.Command{
|
||||||
@ -190,12 +191,11 @@ func purgeOneDriveFolders(
|
|||||||
return clues.New("non-OneDrive item")
|
return clues.New("non-OneDrive item")
|
||||||
}
|
}
|
||||||
|
|
||||||
return onedrive.DeleteItem(
|
return api.DeleteDriveItem(
|
||||||
ctx,
|
ctx,
|
||||||
gs,
|
gs,
|
||||||
*driveFolder.GetParentReference().GetDriveId(),
|
*driveFolder.GetParentReference().GetDriveId(),
|
||||||
*f.GetId(),
|
*f.GetId())
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return purgeFolders(ctx, gc, boundary, "OneDrive Folders", uid, getter, deleter)
|
return purgeFolders(ctx, gc, boundary, "OneDrive Folders", uid, getter, deleter)
|
||||||
|
|||||||
@ -233,7 +233,7 @@ func (gc *GraphConnector) ConsumeRestoreCollections(
|
|||||||
|
|
||||||
switch sels.Service {
|
switch sels.Service {
|
||||||
case selectors.ServiceExchange:
|
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:
|
case selectors.ServiceOneDrive:
|
||||||
status, err = onedrive.RestoreCollections(ctx, creds, backupVersion, gc.Service, dest, opts, dcs, deets, errs)
|
status, err = onedrive.RestoreCollections(ctx, creds, backupVersion, gc.Service, dest, opts, dcs, deets, errs)
|
||||||
case selectors.ServiceSharePoint:
|
case selectors.ServiceSharePoint:
|
||||||
|
|||||||
@ -1,24 +1,35 @@
|
|||||||
package exchange
|
package exchange
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"fmt"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"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/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"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 (
|
const (
|
||||||
// Use large attachment logic for attachments > 3MB
|
// Use large attachment logic for attachments > 3MB
|
||||||
// https://learn.microsoft.com/en-us/graph/outlook-large-attachments
|
// https://learn.microsoft.com/en-us/graph/outlook-large-attachments
|
||||||
largeAttachmentSize = int32(3 * 1024 * 1024)
|
largeAttachmentSize = int32(3 * 1024 * 1024)
|
||||||
attachmentChunkSize = 4 * 1024 * 1024
|
|
||||||
fileAttachmentOdataValue = "#microsoft.graph.fileAttachment"
|
fileAttachmentOdataValue = "#microsoft.graph.fileAttachment"
|
||||||
itemAttachmentOdataValue = "#microsoft.graph.itemAttachment"
|
itemAttachmentOdataValue = "#microsoft.graph.itemAttachment"
|
||||||
referenceAttachmentOdataValue = "#microsoft.graph.referenceAttachment"
|
referenceAttachmentOdataValue = "#microsoft.graph.referenceAttachment"
|
||||||
@ -43,23 +54,30 @@ func attachmentType(attachment models.Attachmentable) models.AttachmentType {
|
|||||||
// uploadAttachment will upload the specified message attachment to M365
|
// uploadAttachment will upload the specified message attachment to M365
|
||||||
func uploadAttachment(
|
func uploadAttachment(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
uploader attachmentUploadable,
|
cli attachmentPoster,
|
||||||
|
userID, containerID, parentItemID string,
|
||||||
attachment models.Attachmentable,
|
attachment models.Attachmentable,
|
||||||
) error {
|
) 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 = clues.Add(
|
||||||
ctx,
|
ctx,
|
||||||
"attachment_size", ptr.Val(attachment.GetSize()),
|
"attachment_size", size,
|
||||||
"attachment_id", ptr.Val(attachment.GetId()),
|
"attachment_id", id,
|
||||||
"attachment_name", clues.Hide(ptr.Val(attachment.GetName())),
|
"attachment_name", clues.Hide(name),
|
||||||
"attachment_type", attachmentType,
|
"attachment_type", attachmentType,
|
||||||
"internal_item_type", getItemAttachmentItemType(attachment),
|
"attachment_odata_type", ptr.Val(attachment.GetOdataType()),
|
||||||
"uploader_item_id", uploader.getItemID())
|
"attachment_outlook_odata_type", getOutlookOdataType(attachment),
|
||||||
|
"parent_item_id", parentItemID)
|
||||||
|
|
||||||
logger.Ctx(ctx).Debug("uploading attachment")
|
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()) {
|
if attachmentType == models.REFERENCE_ATTACHMENTTYPE && ptr.Val(attachment.GetIsInline()) {
|
||||||
logger.Ctx(ctx).Debug("skip uploading inline reference attachment")
|
logger.Ctx(ctx).Debug("skip uploading inline reference attachment")
|
||||||
return nil
|
return nil
|
||||||
@ -69,67 +87,32 @@ func uploadAttachment(
|
|||||||
if attachmentType == models.ITEM_ATTACHMENTTYPE {
|
if attachmentType == models.ITEM_ATTACHMENTTYPE {
|
||||||
a, err := support.ToItemAttachment(attachment)
|
a, err := support.ToItemAttachment(attachment)
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
attachment = a
|
attachment = a
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Item/Reference attachments *or* file attachments < 3MB, use the attachments endpoint
|
// for file attachments sized >= 3MB
|
||||||
if attachmentType != models.FILE_ATTACHMENTTYPE || ptr.Val(attachment.GetSize()) < largeAttachmentSize {
|
if attachmentType == models.FILE_ATTACHMENTTYPE && size >= largeAttachmentSize {
|
||||||
return uploader.uploadSmallAttachment(ctx, attachment)
|
_, 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
|
func getOutlookOdataType(query models.Attachmentable) string {
|
||||||
// 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 := ""
|
|
||||||
attachment, ok := query.(models.ItemAttachmentable)
|
attachment, ok := query.(models.ItemAttachmentable)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return empty
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
item := attachment.GetItem()
|
item := attachment.GetItem()
|
||||||
if item == nil {
|
if item == nil {
|
||||||
return empty
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return ptr.Val(item.GetOdataType())
|
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))
|
assert.NoError(t, err, clues.ToCore(err))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
info, err := RestoreExchangeContact(
|
info, err := RestoreContact(
|
||||||
ctx,
|
ctx,
|
||||||
exchMock.ContactBytes("Corso TestContact"),
|
exchMock.ContactBytes("Corso TestContact"),
|
||||||
suite.gs,
|
suite.ac.Contacts(),
|
||||||
control.Copy,
|
control.Copy,
|
||||||
folderID,
|
folderID,
|
||||||
userID)
|
userID)
|
||||||
@ -135,9 +135,11 @@ func (suite *RestoreIntgSuite) TestRestoreEvent() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
info, err := RestoreExchangeEvent(
|
info, err := RestoreEvent(
|
||||||
ctx,
|
ctx,
|
||||||
test.bytes,
|
test.bytes,
|
||||||
|
suite.ac.Events(),
|
||||||
|
suite.ac.Events(),
|
||||||
suite.gs,
|
suite.gs,
|
||||||
control.Copy,
|
control.Copy,
|
||||||
calendarID,
|
calendarID,
|
||||||
@ -365,11 +367,12 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
destination := test.destination(t, ctx)
|
destination := test.destination(t, ctx)
|
||||||
info, err := RestoreExchangeObject(
|
info, err := RestoreItem(
|
||||||
ctx,
|
ctx,
|
||||||
test.bytes,
|
test.bytes,
|
||||||
test.category,
|
test.category,
|
||||||
control.Copy,
|
control.Copy,
|
||||||
|
suite.ac,
|
||||||
service,
|
service,
|
||||||
destination,
|
destination,
|
||||||
userID,
|
userID,
|
||||||
|
|||||||
@ -25,15 +25,24 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"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
|
// based on the path.CategoryType. All input params are necessary to perform
|
||||||
// the type-specific restore function.
|
// the type-specific restore function.
|
||||||
func RestoreExchangeObject(
|
func RestoreItem(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
bits []byte,
|
bits []byte,
|
||||||
category path.CategoryType,
|
category path.CategoryType,
|
||||||
policy control.CollisionPolicy,
|
policy control.CollisionPolicy,
|
||||||
service graph.Servicer,
|
ac api.Client,
|
||||||
|
gs graph.Servicer,
|
||||||
destination, user string,
|
destination, user string,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
@ -43,26 +52,21 @@ func RestoreExchangeObject(
|
|||||||
|
|
||||||
switch category {
|
switch category {
|
||||||
case path.EmailCategory:
|
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:
|
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:
|
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:
|
default:
|
||||||
return nil, clues.Wrap(clues.New(category.String()), "not supported for Exchange restore")
|
return nil, clues.Wrap(clues.New(category.String()), "not supported for Exchange restore")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreExchangeContact restores a contact to the @bits byte
|
// RestoreContact wraps api.Contacts().PostItem()
|
||||||
// representation of M365 contact object.
|
func RestoreContact(
|
||||||
// @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(
|
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
bits []byte,
|
bits []byte,
|
||||||
service graph.Servicer,
|
cli itemPoster[models.Contactable],
|
||||||
cp control.CollisionPolicy,
|
cp control.CollisionPolicy,
|
||||||
destination, user string,
|
destination, user string,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
@ -73,19 +77,9 @@ func RestoreExchangeContact(
|
|||||||
|
|
||||||
ctx = clues.Add(ctx, "item_id", ptr.Val(contact.GetId()))
|
ctx = clues.Add(ctx, "item_id", ptr.Val(contact.GetId()))
|
||||||
|
|
||||||
response, err := service.Client().
|
_, err = cli.PostItem(ctx, user, destination, contact)
|
||||||
Users().
|
|
||||||
ByUserId(user).
|
|
||||||
ContactFolders().
|
|
||||||
ByContactFolderId(destination).
|
|
||||||
Contacts().
|
|
||||||
Post(ctx, contact, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, graph.Wrap(ctx, err, "uploading Contact")
|
return nil, clues.Stack(err)
|
||||||
}
|
|
||||||
|
|
||||||
if response == nil {
|
|
||||||
return nil, clues.New("nil response from post").WithClues(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info := api.ContactInfo(contact)
|
info := api.ContactInfo(contact)
|
||||||
@ -94,16 +88,13 @@ func RestoreExchangeContact(
|
|||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreExchangeEvent restores a contact to the @bits byte
|
// RestoreEvent wraps api.Events().PostItem()
|
||||||
// representation of M365 event object.
|
func RestoreEvent(
|
||||||
// @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(
|
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
bits []byte,
|
bits []byte,
|
||||||
service graph.Servicer,
|
itemCli itemPoster[models.Eventable],
|
||||||
|
attachmentCli attachmentPoster,
|
||||||
|
gs graph.Servicer,
|
||||||
cp control.CollisionPolicy,
|
cp control.CollisionPolicy,
|
||||||
destination, user string,
|
destination, user string,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
@ -127,34 +118,24 @@ func RestoreExchangeEvent(
|
|||||||
transformedEvent.SetAttachments([]models.Attachmentable{})
|
transformedEvent.SetAttachments([]models.Attachmentable{})
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := service.Client().
|
item, err := itemCli.PostItem(ctx, user, destination, event)
|
||||||
Users().
|
|
||||||
ByUserId(user).
|
|
||||||
Calendars().
|
|
||||||
ByCalendarId(destination).
|
|
||||||
Events().
|
|
||||||
Post(ctx, transformedEvent, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, graph.Wrap(ctx, err, "uploading event")
|
return nil, clues.Stack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response == nil {
|
for _, a := range attached {
|
||||||
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 {
|
|
||||||
if el.Failure() != nil {
|
if el.Failure() != nil {
|
||||||
break
|
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)
|
el.AddRecoverable(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,30 +146,27 @@ func RestoreExchangeEvent(
|
|||||||
return info, el.Failure()
|
return info, el.Failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreMailMessage utility function to place an exchange.Mail
|
// RestoreMessage wraps api.Mail().PostItem(), handling attachment creation along the way
|
||||||
// message into the user's M365 Exchange account.
|
func RestoreMessage(
|
||||||
// @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(
|
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
bits []byte,
|
bits []byte,
|
||||||
service graph.Servicer,
|
itemCli itemPoster[models.Messageable],
|
||||||
|
attachmentCli attachmentPoster,
|
||||||
|
gs graph.Servicer,
|
||||||
cp control.CollisionPolicy,
|
cp control.CollisionPolicy,
|
||||||
destination, user string,
|
destination, user string,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
// Creates messageable object from original bytes
|
// Creates messageable object from original bytes
|
||||||
originalMessage, err := support.CreateMessageFromBytes(bits)
|
msg, err := support.CreateMessageFromBytes(bits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, clues.Wrap(err, "creating mail from bytes").WithClues(ctx)
|
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 (
|
var (
|
||||||
clone = support.ToMessage(originalMessage)
|
clone = support.ToMessage(msg)
|
||||||
valueID = MailRestorePropertyTag
|
valueID = MailRestorePropertyTag
|
||||||
enableValue = RestoreCanonicalEnableValue
|
enableValue = RestoreCanonicalEnableValue
|
||||||
)
|
)
|
||||||
@ -225,80 +203,35 @@ func RestoreMailMessage(
|
|||||||
|
|
||||||
clone.SetSingleValueExtendedProperties(svlep)
|
clone.SetSingleValueExtendedProperties(svlep)
|
||||||
|
|
||||||
if err := SendMailToBackStore(ctx, service, user, destination, clone, errs); err != nil {
|
attached := clone.GetAttachments()
|
||||||
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()
|
|
||||||
|
|
||||||
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
|
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
|
||||||
message.SetAttachments([]models.Attachmentable{})
|
clone.SetAttachments([]models.Attachmentable{})
|
||||||
|
|
||||||
response, err := service.Client().
|
item, err := itemCli.PostItem(ctx, user, destination, clone)
|
||||||
Users().
|
|
||||||
ByUserId(user).
|
|
||||||
MailFolders().
|
|
||||||
ByMailFolderId(destination).
|
|
||||||
Messages().
|
|
||||||
Post(ctx, message, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return graph.Wrap(ctx, err, "restoring mail")
|
return nil, graph.Wrap(ctx, err, "restoring mail message")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response == nil {
|
el := errs.Local()
|
||||||
return clues.New("nil response from post").WithClues(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
for _, a := range attached {
|
||||||
el = errs.Local()
|
|
||||||
id = ptr.Val(response.GetId())
|
|
||||||
uploader = &mailAttachmentUploader{
|
|
||||||
userID: user,
|
|
||||||
folderID: destination,
|
|
||||||
itemID: id,
|
|
||||||
service: service,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, attachment := range attached {
|
|
||||||
if el.Failure() != nil {
|
if el.Failure() != nil {
|
||||||
break
|
return nil, el.Failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := uploadAttachment(ctx, uploader, attachment); err != nil {
|
err := uploadAttachment(
|
||||||
if ptr.Val(attachment.GetOdataType()) == "#microsoft.graph.itemAttachment" {
|
ctx,
|
||||||
name := ptr.Val(attachment.GetName())
|
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).
|
logger.CtxErr(ctx, err).
|
||||||
With("attachment_name", name).
|
With("attachment_name", name).
|
||||||
@ -308,20 +241,18 @@ func SendMailToBackStore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
el.AddRecoverable(clues.Wrap(err, "uploading mail attachment"))
|
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.
|
// store through GraphAPI.
|
||||||
// @param dest: container destination to M365
|
func RestoreCollections(
|
||||||
func RestoreExchangeDataCollections(
|
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
creds account.M365Config,
|
creds account.M365Config,
|
||||||
|
ac api.Client,
|
||||||
gs graph.Servicer,
|
gs graph.Servicer,
|
||||||
dest control.RestoreDestination,
|
dest control.RestoreDestination,
|
||||||
dcs []data.RestoreCollection,
|
dcs []data.RestoreCollection,
|
||||||
@ -365,7 +296,7 @@ func RestoreExchangeDataCollections(
|
|||||||
continue
|
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)
|
metrics = support.CombineMetrics(metrics, temp)
|
||||||
|
|
||||||
@ -387,6 +318,7 @@ func RestoreExchangeDataCollections(
|
|||||||
// restoreCollection handles restoration of an individual collection.
|
// restoreCollection handles restoration of an individual collection.
|
||||||
func restoreCollection(
|
func restoreCollection(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
ac api.Client,
|
||||||
gs graph.Servicer,
|
gs graph.Servicer,
|
||||||
dc data.RestoreCollection,
|
dc data.RestoreCollection,
|
||||||
folderID string,
|
folderID string,
|
||||||
@ -444,11 +376,12 @@ func restoreCollection(
|
|||||||
|
|
||||||
byteArray := buf.Bytes()
|
byteArray := buf.Bytes()
|
||||||
|
|
||||||
info, err := RestoreExchangeObject(
|
info, err := RestoreItem(
|
||||||
ictx,
|
ictx,
|
||||||
byteArray,
|
byteArray,
|
||||||
category,
|
category,
|
||||||
policy,
|
policy,
|
||||||
|
ac,
|
||||||
gs,
|
gs,
|
||||||
folderID,
|
folderID,
|
||||||
user,
|
user,
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const AttachmentChunkSize = 4 * 1024 * 1024
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// item response AdditionalData
|
// item response AdditionalData
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -227,6 +227,8 @@ func (suite *RetryMWIntgSuite) TestRetryMiddleware_RetryRequest_resetBodyAfter50
|
|||||||
adpt, err := mockAdapter(suite.creds, mw)
|
adpt, err := mockAdapter(suite.creds, mw)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
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).
|
_, err = NewService(adpt).
|
||||||
Client().
|
Client().
|
||||||
Users().
|
Users().
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"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"
|
||||||
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
"github.com/alcionai/corso/src/internal/connector/onedrive/metadata"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"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/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testElementsMatch[T any](
|
func testElementsMatch[T any](
|
||||||
@ -118,12 +118,12 @@ func attachmentEqual(
|
|||||||
expected models.Attachmentable,
|
expected models.Attachmentable,
|
||||||
got models.Attachmentable,
|
got models.Attachmentable,
|
||||||
) bool {
|
) bool {
|
||||||
expectedData, err := exchange.GetAttachmentBytes(expected)
|
expectedData, err := api.GetAttachmentContent(expected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
gotData, err := exchange.GetAttachmentBytes(got)
|
gotData, err := api.GetAttachmentContent(got)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -54,9 +55,9 @@ func mustGetDefaultDriveID(
|
|||||||
|
|
||||||
switch backupService {
|
switch backupService {
|
||||||
case path.OneDriveService:
|
case path.OneDriveService:
|
||||||
d, err = service.Client().Users().ByUserId(resourceOwner).Drive().Get(ctx, nil)
|
d, err = api.GetUsersDrive(ctx, service, resourceOwner)
|
||||||
case path.SharePointService:
|
case path.SharePointService:
|
||||||
d, err = service.Client().Sites().BySiteId(resourceOwner).Drive().Get(ctx, nil)
|
d, err = api.GetSitesDefaultDrive(ctx, service, resourceOwner)
|
||||||
default:
|
default:
|
||||||
assert.FailNowf(t, "unknown service type %s", backupService.String())
|
assert.FailNowf(t, "unknown service type %s", backupService.String())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -326,24 +326,3 @@ func GetAllFolders(
|
|||||||
|
|
||||||
return res, el.Failure()
|
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
|
// deletes require unique http clients
|
||||||
// https://github.com/alcionai/corso/issues/2707
|
// 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 {
|
if err != nil {
|
||||||
logger.CtxErr(ictx, err).Errorw("deleting folder")
|
logger.CtxErr(ictx, err).Errorw("deleting folder")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/drives"
|
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"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
|
// TODO: @vkamra verify if var session is the desired input
|
||||||
func driveItemWriter(
|
func driveItemWriter(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
service graph.Servicer,
|
gs graph.Servicer,
|
||||||
driveID, itemID string,
|
driveID, itemID string,
|
||||||
itemSize int64,
|
itemSize int64,
|
||||||
) (io.Writer, error) {
|
) (io.Writer, error) {
|
||||||
session := drives.NewItemItemsItemCreateUploadSessionPostRequestBody()
|
|
||||||
ctx = clues.Add(ctx, "upload_item_id", itemID)
|
ctx = clues.Add(ctx, "upload_item_id", itemID)
|
||||||
|
|
||||||
r, err := service.Client().
|
r, err := api.PostDriveItem(ctx, gs, driveID, itemID)
|
||||||
Drives().
|
|
||||||
ByDriveId(driveID).
|
|
||||||
Items().
|
|
||||||
ByDriveItemId(itemID).
|
|
||||||
CreateUploadSession().
|
|
||||||
Post(ctx, session, nil)
|
|
||||||
if err != nil {
|
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 iw, nil
|
||||||
|
|
||||||
return graph.NewLargeItemWriter(itemID, url, itemSize), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// constructWebURL helper function for recreating the webURL
|
// constructWebURL helper function for recreating the webURL
|
||||||
|
|||||||
@ -154,7 +154,7 @@ func (suite *ItemIntegrationSuite) TestItemWriter() {
|
|||||||
|
|
||||||
srv := suite.service
|
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))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
newFolderName := tester.DefaultTestRestoreDestination("folder").ContainerName
|
newFolderName := tester.DefaultTestRestoreDestination("folder").ContainerName
|
||||||
@ -233,7 +233,7 @@ func (suite *ItemIntegrationSuite) TestDriveGetFolder() {
|
|||||||
|
|
||||||
srv := suite.service
|
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))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
// Lookup a folder that doesn't exist
|
// Lookup a folder that doesn't exist
|
||||||
|
|||||||
@ -155,27 +155,20 @@ func UpdatePermissions(
|
|||||||
// https://github.com/alcionai/corso/issues/2707
|
// https://github.com/alcionai/corso/issues/2707
|
||||||
// this is bad citizenship, and could end up consuming a lot of
|
// this is bad citizenship, and could end up consuming a lot of
|
||||||
// system resources if servicers leak client connections (sockets, etc).
|
// 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]
|
pid, ok := oldPermIDToNewID[p.ID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return clues.New("no new permission id").WithClues(ctx)
|
return clues.New("no new permission id").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = graph.NewService(a).
|
err := api.DeleteDriveItemPermission(
|
||||||
Client().
|
ictx,
|
||||||
Drives().
|
creds,
|
||||||
ByDriveId(driveID).
|
driveID,
|
||||||
Items().
|
itemID,
|
||||||
ByDriveItemId(itemID).
|
pid)
|
||||||
Permissions().
|
|
||||||
ByPermissionId(pid).
|
|
||||||
Delete(graph.ConsumeNTokens(ictx, graph.PermissionsLC), nil)
|
|
||||||
if err != nil {
|
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 := users.NewItemMailFoldersItemMovePostRequestBody()
|
||||||
body.SetDestinationId(ptr.To(to.containerID))
|
body.SetDestinationId(ptr.To(to.containerID))
|
||||||
|
|
||||||
_, err := gc.Service.
|
err := ac.Mail().MoveContainer(ctx, uidn.ID(), from.containerID, body)
|
||||||
Client().
|
|
||||||
Users().
|
|
||||||
ByUserId(uidn.ID()).
|
|
||||||
MailFolders().
|
|
||||||
ByMailFolderId(from.containerID).
|
|
||||||
Move().
|
|
||||||
Post(ctx, body, nil)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
newLoc := expectDeets.MoveLocation(cat.String(), from.locRef, to.locRef)
|
newLoc := expectDeets.MoveLocation(cat.String(), from.locRef, to.locRef)
|
||||||
@ -1083,7 +1076,6 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
|||||||
name: "rename a folder",
|
name: "rename a folder",
|
||||||
updateUserData: func(t *testing.T) {
|
updateUserData: func(t *testing.T) {
|
||||||
for category, d := range dataset {
|
for category, d := range dataset {
|
||||||
cli := gc.Service.Client().Users().ByUserId(uidn.ID())
|
|
||||||
containerID := d.dests[container3].containerID
|
containerID := d.dests[container3].containerID
|
||||||
newLoc := containerRename
|
newLoc := containerRename
|
||||||
|
|
||||||
@ -1103,34 +1095,28 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
|||||||
|
|
||||||
switch category {
|
switch category {
|
||||||
case path.EmailCategory:
|
case path.EmailCategory:
|
||||||
cmf := cli.MailFolders().ByMailFolderId(containerID)
|
body, err := ac.Mail().GetFolder(ctx, uidn.ID(), containerID)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
body, err := cmf.Get(ctx, nil)
|
|
||||||
require.NoError(t, err, "getting mail folder", clues.ToCore(err))
|
|
||||||
|
|
||||||
body.SetDisplayName(&containerRename)
|
body.SetDisplayName(&containerRename)
|
||||||
_, err = cmf.Patch(ctx, body, nil)
|
err = ac.Mail().PatchFolder(ctx, uidn.ID(), containerID, body)
|
||||||
require.NoError(t, err, "updating mail folder name", clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
case path.ContactsCategory:
|
case path.ContactsCategory:
|
||||||
ccf := cli.ContactFolders().ByContactFolderId(containerID)
|
body, err := ac.Contacts().GetFolder(ctx, uidn.ID(), containerID)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
body, err := ccf.Get(ctx, nil)
|
|
||||||
require.NoError(t, err, "getting contact folder", clues.ToCore(err))
|
|
||||||
|
|
||||||
body.SetDisplayName(&containerRename)
|
body.SetDisplayName(&containerRename)
|
||||||
_, err = ccf.Patch(ctx, body, nil)
|
err = ac.Contacts().PatchFolder(ctx, uidn.ID(), containerID, body)
|
||||||
require.NoError(t, err, "updating contact folder name", clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
case path.EventsCategory:
|
case path.EventsCategory:
|
||||||
cbi := cli.Calendars().ByCalendarId(containerID)
|
body, err := ac.Events().GetCalendar(ctx, uidn.ID(), containerID)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
body, err := cbi.Get(ctx, nil)
|
|
||||||
require.NoError(t, err, "getting calendar", clues.ToCore(err))
|
|
||||||
|
|
||||||
body.SetName(&containerRename)
|
body.SetName(&containerRename)
|
||||||
_, err = cbi.Patch(ctx, body, nil)
|
err = ac.Events().PatchCalendar(ctx, uidn.ID(), containerID, body)
|
||||||
require.NoError(t, err, "updating calendar name", clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1146,16 +1132,15 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
|||||||
updateUserData: func(t *testing.T) {
|
updateUserData: func(t *testing.T) {
|
||||||
for category, d := range dataset {
|
for category, d := range dataset {
|
||||||
containerID := d.dests[container1].containerID
|
containerID := d.dests[container1].containerID
|
||||||
cli := gc.Service.Client().Users().ByUserId(uidn.ID())
|
|
||||||
|
|
||||||
switch category {
|
switch category {
|
||||||
case path.EmailCategory:
|
case path.EmailCategory:
|
||||||
_, itemData := generateItemData(t, category, uidn.ID(), mailDBF)
|
_, itemData := generateItemData(t, category, uidn.ID(), mailDBF)
|
||||||
body, err := support.CreateMessageFromBytes(itemData)
|
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)
|
itm, err := ac.Mail().PostItem(ctx, uidn.ID(), containerID, body)
|
||||||
require.NoError(t, err, "posting email item", clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
expectDeets.AddItem(
|
expectDeets.AddItem(
|
||||||
category.String(),
|
category.String(),
|
||||||
@ -1165,10 +1150,10 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
|||||||
case path.ContactsCategory:
|
case path.ContactsCategory:
|
||||||
_, itemData := generateItemData(t, category, uidn.ID(), contactDBF)
|
_, itemData := generateItemData(t, category, uidn.ID(), contactDBF)
|
||||||
body, err := support.CreateContactFromBytes(itemData)
|
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)
|
itm, err := ac.Contacts().PostItem(ctx, uidn.ID(), containerID, body)
|
||||||
require.NoError(t, err, "posting contact item", clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
expectDeets.AddItem(
|
expectDeets.AddItem(
|
||||||
category.String(),
|
category.String(),
|
||||||
@ -1178,10 +1163,10 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
|||||||
case path.EventsCategory:
|
case path.EventsCategory:
|
||||||
_, itemData := generateItemData(t, category, uidn.ID(), eventDBF)
|
_, itemData := generateItemData(t, category, uidn.ID(), eventDBF)
|
||||||
body, err := support.CreateEventFromBytes(itemData)
|
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)
|
itm, err := ac.Events().PostItem(ctx, uidn.ID(), containerID, body)
|
||||||
require.NoError(t, err, "posting events item", clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
expectDeets.AddItem(
|
expectDeets.AddItem(
|
||||||
category.String(),
|
category.String(),
|
||||||
@ -1200,7 +1185,6 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
|||||||
updateUserData: func(t *testing.T) {
|
updateUserData: func(t *testing.T) {
|
||||||
for category, d := range dataset {
|
for category, d := range dataset {
|
||||||
containerID := d.dests[container1].containerID
|
containerID := d.dests[container1].containerID
|
||||||
cli := gc.Service.Client().Users().ByUserId(uidn.ID())
|
|
||||||
|
|
||||||
switch category {
|
switch category {
|
||||||
case path.EmailCategory:
|
case path.EmailCategory:
|
||||||
@ -1208,7 +1192,7 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
|||||||
require.NoError(t, err, "getting message ids", clues.ToCore(err))
|
require.NoError(t, err, "getting message ids", clues.ToCore(err))
|
||||||
require.NotEmpty(t, ids, "message ids in folder")
|
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))
|
require.NoError(t, err, "deleting email item", clues.ToCore(err))
|
||||||
|
|
||||||
expectDeets.RemoveItem(
|
expectDeets.RemoveItem(
|
||||||
@ -1221,7 +1205,7 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
|||||||
require.NoError(t, err, "getting contact ids", clues.ToCore(err))
|
require.NoError(t, err, "getting contact ids", clues.ToCore(err))
|
||||||
require.NotEmpty(t, ids, "contact ids in folder")
|
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))
|
require.NoError(t, err, "deleting contact item", clues.ToCore(err))
|
||||||
|
|
||||||
expectDeets.RemoveItem(
|
expectDeets.RemoveItem(
|
||||||
@ -1234,7 +1218,7 @@ func testExchangeContinuousBackups(suite *BackupOpIntegrationSuite, toggles cont
|
|||||||
require.NoError(t, err, "getting event ids", clues.ToCore(err))
|
require.NoError(t, err, "getting event ids", clues.ToCore(err))
|
||||||
require.NotEmpty(t, ids, "event ids in folder")
|
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))
|
require.NoError(t, err, "deleting calendar", clues.ToCore(err))
|
||||||
|
|
||||||
expectDeets.RemoveItem(
|
expectDeets.RemoveItem(
|
||||||
@ -1666,14 +1650,12 @@ func runDriveIncrementalTest(
|
|||||||
{
|
{
|
||||||
name: "update contents of a file",
|
name: "update contents of a file",
|
||||||
updateFiles: func(t *testing.T) {
|
updateFiles: func(t *testing.T) {
|
||||||
_, err := gc.Service.
|
err := api.PutDriveItemContent(
|
||||||
Client().
|
ctx,
|
||||||
Drives().
|
gc.Service,
|
||||||
ByDriveId(driveID).
|
driveID,
|
||||||
Items().
|
ptr.Val(newFile.GetId()),
|
||||||
ByDriveItemId(ptr.Val(newFile.GetId())).
|
[]byte("new content"))
|
||||||
Content().
|
|
||||||
Put(ctx, []byte("new content"), nil)
|
|
||||||
require.NoErrorf(t, err, "updating file contents: %v", clues.ToCore(err))
|
require.NoErrorf(t, err, "updating file contents: %v", clues.ToCore(err))
|
||||||
// no expectedDeets: neither file id nor location changed
|
// no expectedDeets: neither file id nor location changed
|
||||||
},
|
},
|
||||||
@ -1692,13 +1674,12 @@ func runDriveIncrementalTest(
|
|||||||
parentRef.SetId(&container)
|
parentRef.SetId(&container)
|
||||||
driveItem.SetParentReference(parentRef)
|
driveItem.SetParentReference(parentRef)
|
||||||
|
|
||||||
_, err := gc.Service.
|
err := api.PatchDriveItem(
|
||||||
Client().
|
ctx,
|
||||||
Drives().
|
gc.Service,
|
||||||
ByDriveId(driveID).
|
driveID,
|
||||||
Items().
|
ptr.Val(newFile.GetId()),
|
||||||
ByDriveItemId(ptr.Val(newFile.GetId())).
|
driveItem)
|
||||||
Patch(ctx, driveItem, nil)
|
|
||||||
require.NoError(t, err, "renaming file %v", clues.ToCore(err))
|
require.NoError(t, err, "renaming file %v", clues.ToCore(err))
|
||||||
},
|
},
|
||||||
itemsRead: 1, // .data file for newitem
|
itemsRead: 1, // .data file for newitem
|
||||||
@ -1716,13 +1697,12 @@ func runDriveIncrementalTest(
|
|||||||
parentRef.SetId(&dest)
|
parentRef.SetId(&dest)
|
||||||
driveItem.SetParentReference(parentRef)
|
driveItem.SetParentReference(parentRef)
|
||||||
|
|
||||||
_, err := gc.Service.
|
err := api.PatchDriveItem(
|
||||||
Client().
|
ctx,
|
||||||
Drives().
|
gc.Service,
|
||||||
ByDriveId(driveID).
|
driveID,
|
||||||
Items().
|
ptr.Val(newFile.GetId()),
|
||||||
ByDriveItemId(ptr.Val(newFile.GetId())).
|
driveItem)
|
||||||
Patch(ctx, driveItem, nil)
|
|
||||||
require.NoErrorf(t, err, "moving file between folders %v", clues.ToCore(err))
|
require.NoErrorf(t, err, "moving file between folders %v", clues.ToCore(err))
|
||||||
|
|
||||||
expectDeets.MoveItem(
|
expectDeets.MoveItem(
|
||||||
@ -1737,15 +1717,11 @@ func runDriveIncrementalTest(
|
|||||||
{
|
{
|
||||||
name: "delete file",
|
name: "delete file",
|
||||||
updateFiles: func(t *testing.T) {
|
updateFiles: func(t *testing.T) {
|
||||||
// deletes require unique http clients
|
err := api.DeleteDriveItem(
|
||||||
// https://github.com/alcionai/corso/issues/2707
|
ctx,
|
||||||
err = newDeleteServicer(t).
|
newDeleteServicer(t),
|
||||||
Client().
|
driveID,
|
||||||
Drives().
|
ptr.Val(newFile.GetId()))
|
||||||
ByDriveId(driveID).
|
|
||||||
Items().
|
|
||||||
ByDriveItemId(ptr.Val(newFile.GetId())).
|
|
||||||
Delete(ctx, nil)
|
|
||||||
require.NoErrorf(t, err, "deleting file %v", clues.ToCore(err))
|
require.NoErrorf(t, err, "deleting file %v", clues.ToCore(err))
|
||||||
|
|
||||||
expectDeets.RemoveItem(driveID, makeLocRef(container2), ptr.Val(newFile.GetId()))
|
expectDeets.RemoveItem(driveID, makeLocRef(container2), ptr.Val(newFile.GetId()))
|
||||||
@ -1765,13 +1741,12 @@ func runDriveIncrementalTest(
|
|||||||
parentRef.SetId(&parent)
|
parentRef.SetId(&parent)
|
||||||
driveItem.SetParentReference(parentRef)
|
driveItem.SetParentReference(parentRef)
|
||||||
|
|
||||||
_, err := gc.Service.
|
err := api.PatchDriveItem(
|
||||||
Client().
|
ctx,
|
||||||
Drives().
|
gc.Service,
|
||||||
ByDriveId(driveID).
|
driveID,
|
||||||
Items().
|
child,
|
||||||
ByDriveItemId(child).
|
driveItem)
|
||||||
Patch(ctx, driveItem, nil)
|
|
||||||
require.NoError(t, err, "moving folder", clues.ToCore(err))
|
require.NoError(t, err, "moving folder", clues.ToCore(err))
|
||||||
|
|
||||||
expectDeets.MoveLocation(
|
expectDeets.MoveLocation(
|
||||||
@ -1794,13 +1769,12 @@ func runDriveIncrementalTest(
|
|||||||
parentRef.SetId(&parent)
|
parentRef.SetId(&parent)
|
||||||
driveItem.SetParentReference(parentRef)
|
driveItem.SetParentReference(parentRef)
|
||||||
|
|
||||||
_, err := gc.Service.
|
err := api.PatchDriveItem(
|
||||||
Client().
|
ctx,
|
||||||
Drives().
|
gc.Service,
|
||||||
ByDriveId(driveID).
|
driveID,
|
||||||
Items().
|
child,
|
||||||
ByDriveItemId(child).
|
driveItem)
|
||||||
Patch(ctx, driveItem, nil)
|
|
||||||
require.NoError(t, err, "renaming folder", clues.ToCore(err))
|
require.NoError(t, err, "renaming folder", clues.ToCore(err))
|
||||||
|
|
||||||
containerIDs[containerRename] = containerIDs[container2]
|
containerIDs[containerRename] = containerIDs[container2]
|
||||||
@ -1817,15 +1791,11 @@ func runDriveIncrementalTest(
|
|||||||
name: "delete a folder",
|
name: "delete a folder",
|
||||||
updateFiles: func(t *testing.T) {
|
updateFiles: func(t *testing.T) {
|
||||||
container := containerIDs[containerRename]
|
container := containerIDs[containerRename]
|
||||||
// deletes require unique http clients
|
err := api.DeleteDriveItem(
|
||||||
// https://github.com/alcionai/corso/issues/2707
|
ctx,
|
||||||
err = newDeleteServicer(t).
|
newDeleteServicer(t),
|
||||||
Client().
|
driveID,
|
||||||
Drives().
|
container)
|
||||||
ByDriveId(driveID).
|
|
||||||
Items().
|
|
||||||
ByDriveItemId(container).
|
|
||||||
Delete(ctx, nil)
|
|
||||||
require.NoError(t, err, "deleting folder", clues.ToCore(err))
|
require.NoError(t, err, "deleting folder", clues.ToCore(err))
|
||||||
|
|
||||||
expectDeets.RemoveLocation(driveID, makeLocRef(container1, containerRename))
|
expectDeets.RemoveLocation(driveID, makeLocRef(container1, containerRename))
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"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:")
|
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.
|
// CreateContactFolder makes a contact folder with the displayName of folderName.
|
||||||
@ -72,40 +72,29 @@ func (c Contacts) DeleteContainer(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetItem retrieves a Contactable item.
|
// prefer GetContainerByID where possible.
|
||||||
func (c Contacts) GetItem(
|
// use this only in cases where the models.ContactFolderable
|
||||||
|
// is required.
|
||||||
|
func (c Contacts) GetFolder(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
user, itemID string,
|
userID, containerID string,
|
||||||
immutableIDs bool,
|
) (models.ContactFolderable, error) {
|
||||||
_ *fault.Bus, // no attachments to iterate over, so this goes unused
|
service, err := c.Service()
|
||||||
) (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 {
|
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{
|
config := &users.ItemContactFoldersContactFolderItemRequestBuilderGetRequestConfiguration{
|
||||||
QueryParameters: &users.ItemContactFoldersContactFolderItemRequestBuilderGetQueryParameters{
|
QueryParameters: &users.ItemContactFoldersContactFolderItemRequestBuilderGetQueryParameters{
|
||||||
Select: idAnd(displayName, parentFolderID),
|
Select: idAnd(displayName, parentFolderID),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.Stable.Client().
|
resp, err := service.Client().
|
||||||
Users().
|
Users().
|
||||||
ByUserId(userID).
|
ByUserId(userID).
|
||||||
ContactFolders().
|
ContactFolders().
|
||||||
ByContactFolderId(dirID).
|
ByContactFolderId(containerID).
|
||||||
Get(ctx, config)
|
Get(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, graph.Stack(ctx, err)
|
return nil, graph.Stack(ctx, err)
|
||||||
@ -114,6 +103,41 @@ func (c Contacts) GetContainerByID(
|
|||||||
return resp, nil
|
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
|
// EnumerateContainers iterates through all of the users current
|
||||||
// contacts folders, converting each to a graph.CacheFolder, and calling
|
// contacts folders, converting each to a graph.CacheFolder, and calling
|
||||||
// fn(cf) on each one.
|
// fn(cf) on each one.
|
||||||
@ -187,6 +211,77 @@ func (c Contacts) EnumerateContainers(
|
|||||||
return el.Failure()
|
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
|
// item pager
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -9,46 +9,12 @@ import (
|
|||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
)
|
)
|
||||||
|
|
||||||
// generic drive item getter
|
// ---------------------------------------------------------------------------
|
||||||
func GetDriveItem(
|
// Drives
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUsersDrive(
|
func GetUsersDrive(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@ -89,7 +55,11 @@ func GetDriveRoot(
|
|||||||
srv graph.Servicer,
|
srv graph.Servicer,
|
||||||
driveID string,
|
driveID string,
|
||||||
) (models.DriveItemable, error) {
|
) (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 {
|
if err != nil {
|
||||||
return nil, graph.Wrap(ctx, err, "getting drive root")
|
return nil, graph.Wrap(ctx, err, "getting drive root")
|
||||||
}
|
}
|
||||||
@ -97,6 +67,109 @@ func GetDriveRoot(
|
|||||||
return root, nil
|
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"
|
const itemByPathRawURLFmt = "https://graph.microsoft.com/v1.0/drives/%s/items/%s:/%s"
|
||||||
|
|
||||||
var ErrFolderNotFound = clues.New("folder not found")
|
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.
|
// GetFolderByName will lookup the specified folder by name within the parentFolderID folder.
|
||||||
func GetFolderByName(
|
func GetFolderByName(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
service graph.Servicer,
|
srv graph.Servicer,
|
||||||
driveID, parentFolderID, folder string,
|
driveID, parentFolderID, folder string,
|
||||||
) (models.DriveItemable, error) {
|
) (models.DriveItemable, error) {
|
||||||
// The `Children().Get()` API doesn't yet support $filter, so using that to find a folder
|
// 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
|
// 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
|
// - which allows us to lookup an item by its path relative to the parent ID
|
||||||
rawURL := fmt.Sprintf(itemByPathRawURLFmt, driveID, parentFolderID, folder)
|
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)
|
foundItem, err := builder.Get(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -132,6 +205,30 @@ func GetFolderByName(
|
|||||||
return foundItem, nil
|
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(
|
func PostItemPermissionUpdate(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
service graph.Servicer,
|
service graph.Servicer,
|
||||||
@ -153,3 +250,29 @@ func PostItemPermissionUpdate(
|
|||||||
|
|
||||||
return itm, nil
|
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
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"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
|
// 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
|
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,
|
ctx context.Context,
|
||||||
userID, containerID string,
|
userID, containerID string,
|
||||||
) (graph.Container, error) {
|
) (models.Calendarable, error) {
|
||||||
service, err := c.Service()
|
service, err := c.Service()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, graph.Stack(ctx, err)
|
return nil, graph.Stack(ctx, err)
|
||||||
@ -89,14 +94,27 @@ func (c Events) GetContainerByID(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cal, err := service.Client().
|
resp, err := service.Client().
|
||||||
Users().
|
Users().
|
||||||
ByUserId(userID).
|
ByUserId(userID).
|
||||||
Calendars().
|
Calendars().
|
||||||
ByCalendarId(containerID).
|
ByCalendarId(containerID).
|
||||||
Get(ctx, config)
|
Get(ctx, config)
|
||||||
if err != nil {
|
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
|
return graph.CalendarDisplayable{Calendarable: cal}, nil
|
||||||
@ -141,57 +159,33 @@ func (c Events) GetContainerByName(
|
|||||||
return cal, nil
|
return cal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetItem retrieves an Eventable item.
|
func (c Events) PatchCalendar(
|
||||||
func (c Events) GetItem(
|
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
user, itemID string,
|
userID, containerID string,
|
||||||
immutableIDs bool,
|
body models.Calendarable,
|
||||||
errs *fault.Bus,
|
) error {
|
||||||
) (serialization.Parsable, *details.ExchangeInfo, error) {
|
service, err := c.Service()
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, nil, graph.Stack(ctx, err)
|
return graph.Stack(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ptr.Val(event.GetHasAttachments()) || HasAttachments(event.GetBody()) {
|
_, err = service.Client().
|
||||||
config := &users.ItemEventsItemAttachmentsRequestBuilderGetRequestConfiguration{
|
Users().
|
||||||
QueryParameters: &users.ItemEventsItemAttachmentsRequestBuilderGetQueryParameters{
|
ByUserId(userID).
|
||||||
Expand: []string{"microsoft.graph.itemattachment/item"},
|
Calendars().
|
||||||
},
|
ByCalendarId(containerID).
|
||||||
Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)),
|
Patch(ctx, body, nil)
|
||||||
}
|
if err != nil {
|
||||||
|
return graph.Wrap(ctx, err, "patching event calendar")
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// container pager
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// EnumerateContainers iterates through all of the users current
|
// EnumerateContainers iterates through all of the users current
|
||||||
// calendars, converting each to a graph.CacheFolder, and
|
// calendars, converting each to a graph.CacheFolder, and
|
||||||
// calling fn(cf) on each one.
|
// calling fn(cf) on each one.
|
||||||
@ -272,6 +266,176 @@ const (
|
|||||||
eventBetaDeltaURLTemplate = "https://graph.microsoft.com/beta/users/%s/calendars/%s/events/delta"
|
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
|
// item pager
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoft/kiota-abstractions-go/serialization"
|
"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
|
// 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
|
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,
|
ctx context.Context,
|
||||||
userID, dirID string,
|
userID, containerID string,
|
||||||
) (graph.Container, error) {
|
) (models.MailFolderable, error) {
|
||||||
service, err := c.Service()
|
service, err := c.Service()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, graph.Stack(ctx, err)
|
return nil, graph.Stack(ctx, err)
|
||||||
@ -132,7 +137,7 @@ func (c Mail) GetContainerByID(
|
|||||||
Users().
|
Users().
|
||||||
ByUserId(userID).
|
ByUserId(userID).
|
||||||
MailFolders().
|
MailFolders().
|
||||||
ByMailFolderId(dirID).
|
ByMailFolderId(containerID).
|
||||||
Get(ctx, config)
|
Get(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, graph.Stack(ctx, err)
|
return nil, graph.Stack(ctx, err)
|
||||||
@ -141,6 +146,175 @@ func (c Mail) GetContainerByID(
|
|||||||
return resp, nil
|
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
|
// GetItem retrieves a Messageable item. If the item contains an attachment, that
|
||||||
// attachment is also downloaded.
|
// attachment is also downloaded.
|
||||||
func (c Mail) GetItem(
|
func (c Mail) GetItem(
|
||||||
@ -265,109 +439,126 @@ func (c Mail) GetItem(
|
|||||||
return mail, MailInfo(mail, size), nil
|
return mail, MailInfo(mail, size), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type mailFolderPager struct {
|
func (c Mail) PostItem(
|
||||||
service graph.Servicer
|
ctx context.Context,
|
||||||
builder *users.ItemMailFoldersRequestBuilder
|
userID, containerID string,
|
||||||
}
|
body models.Messageable,
|
||||||
|
) (models.Messageable, error) {
|
||||||
func NewMailFolderPager(service graph.Servicer, user string) mailFolderPager {
|
service, err := c.Service()
|
||||||
// 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 {
|
if err != nil {
|
||||||
return nil, graph.Stack(ctx, err)
|
return nil, graph.Stack(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return page, nil
|
itm, err := service.
|
||||||
}
|
Client().
|
||||||
|
Users().
|
||||||
func (p *mailFolderPager) setNext(nextLink string) {
|
ByUserId(userID).
|
||||||
p.builder = users.NewItemMailFoldersRequestBuilder(nextLink, p.service.Adapter())
|
MailFolders().
|
||||||
}
|
ByMailFolderId(containerID).
|
||||||
|
Messages().
|
||||||
func (p *mailFolderPager) valuesIn(pl PageLinker) ([]models.MailFolderable, error) {
|
Post(ctx, body, nil)
|
||||||
// Ideally this should be `users.ItemMailFoldersResponseable`, but
|
if err != nil {
|
||||||
// that is not a thing as stable returns different result
|
return nil, graph.Wrap(ctx, err, "creating mail message")
|
||||||
page, ok := pl.(models.MailFolderCollectionResponseable)
|
|
||||||
if !ok {
|
|
||||||
return nil, clues.New("converting to ItemMailFoldersResponseable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
func (c Mail) DeleteItem(
|
||||||
// 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,
|
ctx context.Context,
|
||||||
userID, baseDirID string,
|
userID, itemID string,
|
||||||
fn func(graph.CachedContainer) error,
|
) error {
|
||||||
errs *fault.Bus,
|
// 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 {
|
) error {
|
||||||
service, err := c.Service()
|
service, err := c.Service()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return graph.Stack(ctx, err)
|
return graph.Stack(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
el := errs.Local()
|
_, err = service.
|
||||||
|
Client().
|
||||||
pgr := NewMailFolderPager(service, userID)
|
Users().
|
||||||
|
ByUserId(userID).
|
||||||
for {
|
MailFolders().
|
||||||
if el.Failure() != nil {
|
ByMailFolderId(containerID).
|
||||||
break
|
Messages().
|
||||||
}
|
ByMessageId(parentItemID).
|
||||||
|
Attachments().
|
||||||
page, err := pgr.getPage(ctx)
|
Post(ctx, body, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return graph.Stack(ctx, err)
|
return graph.Wrap(ctx, err, "uploading small mail attachment")
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
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 nil, clues.Wrap(err, "serializing attachment content").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user