Needed some changes in the func names. ``` bash $ sed -i "s/By\([a-zA-Z]*\)IdString/By\1Id/g" **/*.go ``` <!-- PR description--> --- #### Does this PR need a docs update or release note? - [ ] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [x] ⛔ No #### Type of change <!--- Please check the type of change your PR introduces: ---> - [ ] 🌻 Feature - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * #<issue> #### Test Plan <!-- How will this be tested prior to merging.--> - [x] 💪 Manual - [x] ⚡ Unit test - [x] 💚 E2E
343 lines
8.8 KiB
Go
343 lines
8.8 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/alcionai/clues"
|
|
"github.com/microsoftgraph/msgraph-sdk-go/drives"
|
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
|
|
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
|
"github.com/alcionai/corso/src/internal/m365/graph"
|
|
"github.com/alcionai/corso/src/pkg/control"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// controller
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func (c Client) Drives() Drives {
|
|
return Drives{c}
|
|
}
|
|
|
|
// Drives is an interface-compliant provider of the client.
|
|
type Drives struct {
|
|
Client
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Folders
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const (
|
|
itemByPathRawURLFmt = "https://graph.microsoft.com/v1.0/drives/%s/items/%s:/%s"
|
|
createLinkShareURLFmt = "https://graph.microsoft.com/beta/drives/%s/items/%s/createLink"
|
|
)
|
|
|
|
var ErrFolderNotFound = clues.New("folder not found")
|
|
|
|
// GetFolderByName will lookup the specified folder by name within the parentFolderID folder.
|
|
func (c Drives) GetFolderByName(
|
|
ctx context.Context,
|
|
driveID, parentFolderID, folderName string,
|
|
) (models.DriveItemable, error) {
|
|
// The `Children().Get()` API doesn't yet support $filter, so using that to find a folder
|
|
// will be sub-optimal.
|
|
// Instead, we leverage OneDrive 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
|
|
rawURL := fmt.Sprintf(itemByPathRawURLFmt, driveID, parentFolderID, folderName)
|
|
builder := drives.NewItemItemsDriveItemItemRequestBuilder(rawURL, c.Stable.Adapter())
|
|
|
|
foundItem, err := builder.Get(ctx, nil)
|
|
if err != nil {
|
|
if graph.IsErrDeletedInFlight(err) {
|
|
return nil, graph.Stack(ctx, clues.Stack(ErrFolderNotFound, err))
|
|
}
|
|
|
|
return nil, graph.Wrap(ctx, err, "getting folder")
|
|
}
|
|
|
|
// Check if the item found is a folder, fail the call if not
|
|
if foundItem.GetFolder() == nil {
|
|
return nil, graph.Wrap(ctx, ErrFolderNotFound, "item is not a folder")
|
|
}
|
|
|
|
return foundItem, nil
|
|
}
|
|
|
|
func (c Drives) GetRootFolder(
|
|
ctx context.Context,
|
|
driveID string,
|
|
) (models.DriveItemable, error) {
|
|
root, err := c.Stable.
|
|
Client().
|
|
Drives().
|
|
ByDriveId(driveID).
|
|
Root().
|
|
Get(ctx, nil)
|
|
if err != nil {
|
|
return nil, graph.Wrap(ctx, err, "getting drive root")
|
|
}
|
|
|
|
return root, nil
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Items
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// generic drive item getter
|
|
func (c Drives) GetItem(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
) (models.DriveItemable, error) {
|
|
di, err := c.Stable.
|
|
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 (c Drives) NewItemContentUpload(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
) (models.UploadSessionable, error) {
|
|
session := drives.NewItemItemsItemCreateUploadSessionPostRequestBody()
|
|
|
|
r, err := c.Stable.
|
|
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
|
|
}
|
|
|
|
//nolint:lll
|
|
const itemChildrenRawURLFmt = "https://graph.microsoft.com/v1.0/drives/%s/items/%s/children?@microsoft.graph.conflictBehavior=%s"
|
|
|
|
const (
|
|
conflictBehaviorFail = "fail"
|
|
conflictBehaviorRename = "rename"
|
|
conflictBehaviorReplace = "replace"
|
|
)
|
|
|
|
// PostItemInContainer creates a new item in the specified folder
|
|
func (c Drives) PostItemInContainer(
|
|
ctx context.Context,
|
|
driveID, parentFolderID string,
|
|
newItem models.DriveItemable,
|
|
onCollision control.CollisionPolicy,
|
|
) (models.DriveItemable, error) {
|
|
// graph api has no policy for Skip; instead we wrap the same-name failure
|
|
// as a graph.ErrItemAlreadyExistsConflict.
|
|
conflictBehavior := conflictBehaviorFail
|
|
|
|
switch onCollision {
|
|
case control.Replace:
|
|
conflictBehavior = conflictBehaviorReplace
|
|
case control.Copy:
|
|
conflictBehavior = conflictBehaviorRename
|
|
}
|
|
|
|
// Graph SDK doesn't yet provide a POST method for `/children` so we set the `rawUrl` ourselves as recommended
|
|
// here: https://github.com/microsoftgraph/msgraph-sdk-go/issues/155#issuecomment-1136254310
|
|
rawURL := fmt.Sprintf(itemChildrenRawURLFmt, driveID, parentFolderID, conflictBehavior)
|
|
builder := drives.NewItemItemsRequestBuilder(rawURL, c.Stable.Adapter())
|
|
|
|
newItem, err := builder.Post(ctx, newItem, nil)
|
|
if err != nil {
|
|
if graph.IsErrItemAlreadyExistsConflict(err) {
|
|
return nil, clues.Stack(graph.ErrItemAlreadyExistsConflict, err)
|
|
}
|
|
|
|
return nil, graph.Wrap(ctx, err, "creating item in folder")
|
|
}
|
|
|
|
return newItem, nil
|
|
}
|
|
|
|
func (c Drives) PatchItem(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
item models.DriveItemable,
|
|
) error {
|
|
_, err := c.Stable.
|
|
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 (c Drives) PutItemContent(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
content []byte,
|
|
) error {
|
|
_, err := c.Stable.
|
|
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 (c Drives) DeleteItem(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
) error {
|
|
// deletes require unique http clients
|
|
// https://github.com/alcionai/corso/issues/2707
|
|
srv, err := c.Service()
|
|
if err != nil {
|
|
return graph.Wrap(ctx, err, "creating adapter to delete item permission")
|
|
}
|
|
|
|
err = srv.
|
|
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
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Permissions
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func (c Drives) GetItemPermission(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
) (models.PermissionCollectionResponseable, error) {
|
|
perm, err := c.Stable.
|
|
Client().
|
|
Drives().
|
|
ByDriveId(driveID).
|
|
Items().
|
|
ByDriveItemId(itemID).
|
|
Permissions().
|
|
Get(ctx, nil)
|
|
if err != nil {
|
|
return nil, graph.Wrap(ctx, err, "getting item permission").With("item_id", itemID)
|
|
}
|
|
|
|
return perm, nil
|
|
}
|
|
|
|
func (c Drives) PostItemPermissionUpdate(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
body *drives.ItemItemsItemInvitePostRequestBody,
|
|
) (drives.ItemItemsItemInviteResponseable, error) {
|
|
ctx = graph.ConsumeNTokens(ctx, graph.PermissionsLC)
|
|
|
|
itm, err := c.Stable.
|
|
Client().
|
|
Drives().
|
|
ByDriveId(driveID).
|
|
Items().
|
|
ByDriveItemId(itemID).
|
|
Invite().
|
|
Post(ctx, body, nil)
|
|
if err != nil {
|
|
return nil, graph.Wrap(ctx, err, "posting permissions")
|
|
}
|
|
|
|
return itm, nil
|
|
}
|
|
|
|
func (c Drives) DeleteItemPermission(
|
|
ctx context.Context,
|
|
driveID, itemID, permissionID string,
|
|
) error {
|
|
// deletes require unique http clients
|
|
// https://github.com/alcionai/corso/issues/2707
|
|
srv, err := c.Service()
|
|
if err != nil {
|
|
return graph.Wrap(ctx, err, "creating adapter to delete item permission")
|
|
}
|
|
|
|
err = srv.
|
|
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
|
|
}
|
|
|
|
func (c Drives) PostItemLinkShareUpdate(
|
|
ctx context.Context,
|
|
driveID, itemID string,
|
|
body *drives.ItemItemsItemCreateLinkPostRequestBody,
|
|
) (models.Permissionable, error) {
|
|
ctx = graph.ConsumeNTokens(ctx, graph.PermissionsLC)
|
|
|
|
// We are using the beta version of the endpoint. This allows us
|
|
// to add recipients in the same request as well as to make it not
|
|
// send out and email for every link share the user gets added to.
|
|
rawURL := fmt.Sprintf(createLinkShareURLFmt, driveID, itemID)
|
|
builder := drives.NewItemItemsItemCreateLinkRequestBuilder(rawURL, c.Stable.Adapter())
|
|
|
|
itm, err := builder.Post(ctx, body, nil)
|
|
if err != nil {
|
|
return nil, graph.Wrap(ctx, err, "creating link share")
|
|
}
|
|
|
|
return itm, nil
|
|
}
|
|
|
|
// DriveItemCollisionKeyy constructs a key from the item name.
|
|
// collision keys are used to identify duplicate item conflicts for handling advanced restoration config.
|
|
func DriveItemCollisionKey(item models.DriveItemable) string {
|
|
if item == nil {
|
|
return ""
|
|
}
|
|
|
|
return ptr.Val(item.GetName())
|
|
}
|