/*! * Copyright (c) Microsoft Corporation and contributors. All rights reserved. * Licensed under the MIT License. */ import type { IOdspAuthRequestInfo } from "./odspAuth.js"; import { getSiteUrl } from "./odspDocLibUtils.js"; import { throwOdspNetworkError } from "./odspErrorUtils.js"; import { getAsync, putAsync } from "./odspRequest.js"; interface IOdspUser { displayName: string; email?: string; id?: string; } interface IOdspGroup { displayName: string; email?: string; } interface IOdspDriveQuota { deleted: number; fileCount: number; remaining: number; state: string; total: number; used: number; } interface IOdspEntity { user?: IOdspUser; group?: IOdspGroup; } interface IOdspDriveInfo { id: string; createdDateTime: string; description: string; driveType: string; lastModifiedDateTime: string; name: string; webUrl: string; createdBy: IOdspEntity; lastModifiedBy: IOdspEntity; owner: IOdspEntity; quota: IOdspDriveQuota; } /** * @internal */ export interface IOdspDriveItem { path: string; name: string; driveId: string; itemId: string; isFolder: boolean; } // eslint-disable-next-line jsdoc/require-description -- TODO: Add documentation /** * @internal */ export async function getDriveItemByRootFileName( server: string, account: string | undefined, path: string, authRequestInfo: IOdspAuthRequestInfo, create: boolean, driveId?: string, ): Promise { const accountPath = account === undefined ? "" : `/${account}`; let getDriveItemUrl: string; if (driveId !== undefined && driveId !== "") { const encodedDrive = encodeURIComponent(driveId); getDriveItemUrl = `${getSiteUrl( server, )}${accountPath}/_api/v2.1/drives/${encodedDrive}/root:${path}:`; } else { getDriveItemUrl = `${getSiteUrl(server)}${accountPath}/_api/v2.1/drive/root:${path}:`; } return getDriveItem(getDriveItemUrl, authRequestInfo, create); } // eslint-disable-next-line jsdoc/require-description -- TODO: Add documentation /** * @internal */ export async function getDriveItemByServerRelativePath( server: string, serverRelativePath: string, authRequestInfo: IOdspAuthRequestInfo, create: boolean, ): Promise { let account = ""; const pathParts = serverRelativePath.split("/"); if (serverRelativePath.startsWith("/")) { pathParts.shift(); } if (pathParts.length === 0) { throw new Error(`Invalid serverRelativePath ${serverRelativePath}`); } if ( pathParts.length >= 2 && (pathParts[0] === "personal" || pathParts[0] === "teams" || pathParts[0] === "sites") ) { account = `${pathParts.shift()}/${pathParts.shift()}`; } const library = pathParts.shift(); if (!library) { // Default drive/library return getDriveItemByRootFileName(server, account, "/", authRequestInfo, create); } const path = `/${pathParts.join("/")}`; const driveId = await getDriveId(server, account, library, authRequestInfo); const getDriveItemUrl = `${getSiteUrl(server)}/_api/v2.1/drives/${driveId}/root:${path}:`; return getDriveItem(getDriveItemUrl, authRequestInfo, create); } // eslint-disable-next-line jsdoc/require-description -- TODO: Add documentation /** * @internal */ export async function getDriveItemFromDriveAndItem( server: string, drive: string, item: string, authRequestInfo: IOdspAuthRequestInfo, ): Promise { const url = `${getSiteUrl(server)}/_api/v2.1/drives/${drive}/items/${item}`; return getDriveItem(url, authRequestInfo, false); } // eslint-disable-next-line jsdoc/require-description -- TODO: Add documentation /** * @internal */ export async function getChildrenByDriveItem( driveItem: IOdspDriveItem, server: string, authRequestInfo: IOdspAuthRequestInfo, ): Promise { if (!driveItem.isFolder) { return []; } let url = `${getSiteUrl(server)}/_api/v2.1/drives/${driveItem.driveId}/items/${ driveItem.itemId }/children`; let children: unknown[] = []; do { const response = await getAsync(url, authRequestInfo); if (response.status !== 200) { // pre-0.58 error message: unableToGetChildren throwOdspNetworkError("Unable to get driveItem children", response.status, response); } // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: use stronger typing here. const getChildrenResult = await response.json(); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access children = [...children, ...getChildrenResult.value]; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access url = getChildrenResult["@odata.nextLink"]; } while (url); return children.map((child) => toIODSPDriveItem(child)); } async function getDriveItem( getDriveItemUrl: string, authRequestInfo: IOdspAuthRequestInfo, create: boolean, ): Promise { let response = await getAsync(getDriveItemUrl, authRequestInfo); if (response.status !== 200) { if (!create) { // pre-0.58 error message: unableToGetDriveItemIdFromPath throwOdspNetworkError( "Unable to get drive/item id from path", response.status, response, ); } // Try creating the file const contentUri = `${getDriveItemUrl}/content`; const createResultResponse = await putAsync(contentUri, authRequestInfo); if (createResultResponse.status !== 201) { // pre-0.58 error message: failedToCreateFile throwOdspNetworkError( "Failed to create file", createResultResponse.status, createResultResponse, ); } response = await getAsync(getDriveItemUrl, authRequestInfo); if (response.status !== 200) { // pre-0.58 error message: unableToGetDriveItemIdFromPath throwOdspNetworkError( "Unable to get drive/item id from path after creating", response.status, response, ); } } // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: use stronger typing here. const getDriveItemResult = await response.json(); return toIODSPDriveItem(getDriveItemResult); } // eslint-disable-next-line jsdoc/require-description -- TODO: Add documentation /** * @legacy @beta */ export async function getDriveId( server: string, account: string, library: string | undefined, authRequestInfo: IOdspAuthRequestInfo, ): Promise { if (library === undefined) { const drive = await getDefaultDrive(server, account, authRequestInfo); return drive.id; } const drives = await getDrives(server, account, authRequestInfo); const accountPath = account ? `/${account}` : ""; const drivePath = encodeURI(`${getSiteUrl(server)}${accountPath}/${library}`); const index = drives.findIndex((value) => value.webUrl === drivePath); if (index === -1) { throw new Error(`Drive ${drivePath} not found.`); } return drives[index].id; } async function getDefaultDrive( server: string, account: string, authRequestInfo: IOdspAuthRequestInfo, ): Promise { const response = await getDriveResponse("drive", server, account, authRequestInfo); const getDriveResult = (await response.json()) as IOdspDriveInfo; return getDriveResult; } async function getDrives( server: string, account: string, authRequestInfo: IOdspAuthRequestInfo, ): Promise { const response = await getDriveResponse("drives", server, account, authRequestInfo); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: use stronger typing here. const getDriveResult = await response.json(); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return getDriveResult.value as IOdspDriveInfo[]; } async function getDriveResponse( routeTail: "drive" | "drives", server: string, account: string, authRequestInfo: IOdspAuthRequestInfo, ): Promise { const accountPath = account ? `/${account}` : ""; const getDriveUrl = `${getSiteUrl(server)}${accountPath}/_api/v2.1/${routeTail}`; const response = await getAsync(getDriveUrl, authRequestInfo); if (response.status !== 200) { throwOdspNetworkError( // pre-0.58 error message: failedToGetDriveResponse `Failed to get response from /${routeTail} endpoint`, response.status, response, undefined, { route: routeTail }, ); } return response; } // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: use a real type here function toIODSPDriveItem(parsedDriveItemBody: any): IOdspDriveItem { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const path = parsedDriveItemBody.parentReference.path ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call parsedDriveItemBody.parentReference.path.split("root:")[1] : "/"; return { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment path, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access name: parsedDriveItemBody.name, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access driveId: parsedDriveItemBody.parentReference.driveId, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access itemId: parsedDriveItemBody.id, // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access isFolder: !!parsedDriveItemBody.folder, }; }