diff --git a/docs/graph/appCatalog.md b/docs/graph/appCatalog.md new file mode 100644 index 000000000..9d664fcad --- /dev/null +++ b/docs/graph/appCatalog.md @@ -0,0 +1,121 @@ +# @pnp/graph/appcatalog + +The ability to use Teams App Catalog + +## AppCatalog, IAppCatalog + +[![Invokable Banner](https://img.shields.io/badge/Invokable-informational.svg)](../concepts/invokable.md) [![Selective Imports Banner](https://img.shields.io/badge/Selective%20Imports-informational.svg)](../concepts/selective-imports.md) + +## Get Teams Apps in App Catalog + +Using teamsApps() you get the Teams AppCatalog + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/teams"; +import "@pnp/graph/appCatalog"; + +const graph = graphfi(...); + +const apps = await graph.appCatalog.teamsApps(); + +``` +## Get Teams Apps by Id + +Using getById() you get the Teams App by Id + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/teams"; +import "@pnp/graph/appCatalog"; + +const graph = graphfi(...); + +const apps = await graph.appCatalog.teamsApps.getById('{teams app id}')(); + +``` +## Add a Teams App + + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/teams"; +import "@pnp/graph/appCatalog"; + +const graph = graphfi(...); +const appPackage = {...} as Blob; + +//second parameter is "Requires Approval" +const app = await graph.appCatalog.teamsApps.getById('{teams app id}').add(appPackage, false); + +``` +## Update a Teams App + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/teams"; +import "@pnp/graph/appCatalog"; + +const graph = graphfi(...); +const appPackage = {...} as Blob; + +//second parameter is "Requires Approval" +const app = await graph.appCatalog.teamsApps.getById('{teams app id}').update(appPackage, false); + +``` +## Delete a Teams App + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/teams"; +import "@pnp/graph/appCatalog"; + +const graph = graphfi(...); + +//delete a Teams App +await graph.appCatalog.teamsApps.getById(app).delete(); + +// delete an un-approved Teams App requires the app definition id. +// sample is just selecting the first app definition. +const appDefinition = (await graph.appCatalog.teamsApps.getById("{teams app id}")()).appDefinitions[0]; +await graph.appCatalog.teamsApps.getById(app).delete(appDefinition); + +``` +## Get Teams App Definitions + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/teams"; +import "@pnp/graph/appCatalog"; + +const graph = graphfi(...); + +//get teams app definitions +await graph.appCatalog.teamsApps.getById(`{teams app id}`).appDefinitions(); + +``` +## Get Teams App Definitions by Id + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/teams"; +import "@pnp/graph/appCatalog"; + +const graph = graphfi(...); + +//get teams app definitions +await graph.appCatalog.teamsApps.getById(`{teams app id}`).appDefinitions.getById(`{Teams App Definition Id}`) + +``` +## Get Bot associated with Teams App Definition + +```TypeScript +import { graphfi } from "@pnp/graph"; +import "@pnp/graph/teams"; +import "@pnp/graph/appCatalog"; + +const graph = graphfi(...); + +await graph.appCatalog.teamsApps.getById(`{teams app id}`).appDefinitions.getById(`{Teams App Definition Id}`).bot(); + +``` \ No newline at end of file diff --git a/packages/graph/appCatalog/index.ts b/packages/graph/appCatalog/index.ts new file mode 100644 index 000000000..8fba9b1aa --- /dev/null +++ b/packages/graph/appCatalog/index.ts @@ -0,0 +1,22 @@ +import { GraphFI } from "../fi.js"; +import { AppCatalog, IAppCatalog} from "./types.js"; + + +export { + AppCatalog, + IAppCatalog, +} from "./types.js"; + +declare module "../fi" { + interface GraphFI { + readonly appCatalog: IAppCatalog; + } +} + +Reflect.defineProperty(GraphFI.prototype, "appCatalog", { + configurable: true, + enumerable: true, + get: function (this: GraphFI) { + return this.create(AppCatalog); + }, +}); diff --git a/packages/graph/appCatalog/types.ts b/packages/graph/appCatalog/types.ts new file mode 100644 index 000000000..cad91a185 --- /dev/null +++ b/packages/graph/appCatalog/types.ts @@ -0,0 +1,122 @@ +import { + AppCatalogs as IAppCatalogsType, + TeamsApp as ITeamsAppType, + TeamsAppDefinition as ITeamsAppDefinitionType, + TeamworkBot as ITeamworkBot } from "@microsoft/microsoft-graph-types"; +import { _GraphCollection, graphInvokableFactory, _GraphInstance, graphPost, graphDelete, graphGet } from "../graphqueryable.js"; +import { IGetById, defaultPath, getById } from "../decorators.js"; +import { InjectHeaders } from "@pnp/queryable/index.js"; + +/** + * AppCatalogs + */ + +@defaultPath("appCatalogs") +export class _AppCatalog extends _GraphInstance { + /** + * Get teams apps in appCatalog + * + */ + public get teamsApps(): ITeamsApps { + return TeamsApps(this); + } + +} +export interface IAppCatalog extends _AppCatalog {} +export const AppCatalog = graphInvokableFactory(_AppCatalog); + +/** + * AppDefinition + */ +export class _AppDefinition extends _GraphInstance { + /** + * Gets bot associated with app + * + */ + public async bot(): Promise{ + return graphGet(AppDefinitions(this, "/bot")); + } +} +export interface IAppDefinition extends _AppDefinition { } +export const AppDefinition = graphInvokableFactory(_AppDefinition); + +/** + * AppDefinitions + */ + +@defaultPath("appDefinitions") +@getById(AppDefinition) +export class _AppDefinitions extends _GraphCollection {} +export interface IAppDefinitions extends _AppDefinitions, IGetById {} +export const AppDefinitions = graphInvokableFactory(_AppDefinitions); + + +/** + * TeamsApp + */ +export class _TeamsApp extends _GraphInstance { + /** + * Get app definitions + * + */ + public get appDefinitions(): IAppDefinitions { + return AppDefinitions(this); + } + + /** + * Deletes a Teams App + * + */ + public async delete(appDefinitionId?: string): Promise { + // Un-approved apps must be deleted differently. https://learn.microsoft.com/en-us/graph/api/teamsapp-delete?view=graph-rest-1.0&tabs=http#permissions + if(appDefinitionId){ + return graphDelete(AppDefinitions(this,`/${appDefinitionId}`)); + } + return graphDelete(this); + } + + /** + * Updates a Teams App + * + * @param zip zip file of app + * @param requiresReview This optional query parameter triggers the app review process. Users with admin privileges can submit apps without triggering a review. + */ + public async update(zip: Blob, requiresReview = false): Promise { + const q = AppDefinitions(this,`?$requiresReview=${requiresReview}`); + q.using(InjectHeaders({ + "Content-Type": "application/zip", + })); + + return graphPost(q, { body: zip }); + } +} + +export interface ITeamsApp extends _TeamsApp{} +export const TeamsApp = graphInvokableFactory(_TeamsApp); + + +/** + * TeamsApps + */ + +@defaultPath("teamsApps") +@getById(TeamsApp) +export class _TeamsApps extends _GraphCollection { + /** + * Adds a Teams App + * + * @param zip zip file of app + * @param requiresReview This optional query parameter triggers the app review process. Users with admin privileges can submit apps without triggering a review. + * + */ + public async add(zip: Blob, requiresReview = false): Promise { + const q = TeamsApp(this, `?requiresReview=${requiresReview}`); + q.using(InjectHeaders({ + "Content-Type": "application/zip", + })); + + return graphPost(q, { body: zip }); + } +} +export interface ITeamsApps extends _TeamsApps, IGetById{} +export const TeamsApps = graphInvokableFactory(_TeamsApps); diff --git a/packages/graph/presets/all.ts b/packages/graph/presets/all.ts index 641972213..2edcc11ad 100644 --- a/packages/graph/presets/all.ts +++ b/packages/graph/presets/all.ts @@ -1,3 +1,4 @@ +import "../appCatalog/index.js"; import "../attachments/index.js"; import "../calendars/index.js"; import "../cloud-communications/index.js"; @@ -20,6 +21,7 @@ import "../subscriptions/index.js"; import "../teams/index.js"; import "../users/index.js"; +export * from "../appCatalog/index.js"; export * from "../attachments/index.js"; export * from "../calendars/index.js"; export * from "../cloud-communications/index.js"; diff --git a/packages/graph/teams/types.ts b/packages/graph/teams/types.ts index 7ac7c401c..fce469c61 100644 --- a/packages/graph/teams/types.ts +++ b/packages/graph/teams/types.ts @@ -180,7 +180,7 @@ export interface IMessage extends _Message { } export const Message = graphInvokableFactory(_Message); /** - * Channels + * Messages */ @defaultPath("messages") @getById(Message) diff --git a/test/graph/appCatalogs.ts b/test/graph/appCatalogs.ts new file mode 100644 index 000000000..39aa54dc7 --- /dev/null +++ b/test/graph/appCatalogs.ts @@ -0,0 +1,47 @@ +import { expect } from "chai"; +import "@pnp/graph/teams"; +import "@pnp/graph/appCatalog"; +import { pnpTest } from "../pnp-test.js"; + +describe.only("AppCatalog", function () { + + before(async function () { + + if (!this.pnp.settings.enableWebTests) { + this.skip(); + } + }); + + it("teamsApps", pnpTest("32d84a70-52cb-47c8-8957-cda902c07d85", async function () { + const apps = await this.pnp.graph.appCatalog.teamsApps(); + return expect(apps).to.be.an("array") && expect(apps[0]).to.haveOwnProperty("id"); + })); + + it("teamsApps - getById()", pnpTest("17bfb2cd-8fd3-41d3-a387-2fcf410b7100", async function () { + let passed = false; + const apps = await this.pnp.graph.appCatalog.teamsApps(); + if (apps.length > 0) { + const app = await this.pnp.graph.appCatalog.teamsApps.getById(apps[0].id)(); + passed = (app.id === apps[0].id); + } + return expect(passed).is.true; + })); + + it("appDefinitions", pnpTest("63c8ef41-067f-4f58-bd78-9b5d8d60b5b4", async function () { + const apps = await this.pnp.graph.appCatalog.teamsApps(); + const appDefinitions = await this.pnp.graph.appCatalog.teamsApps.getById(apps[0].id).appDefinitions(); + return expect(appDefinitions).to.be.an("array") && expect(appDefinitions[0]).to.haveOwnProperty("id"); + })); + + it("appDefinitions - getById()", pnpTest("11dce742-2aeb-4b8e-8967-6f73b7fd55d6", async function () { + let passed = false; + const apps = await this.pnp.graph.appCatalog.teamsApps(); + const appDefinitions = await this.pnp.graph.appCatalog.teamsApps.getById(apps[0].id).appDefinitions(); + + if (apps.length > 0) { + const def = await this.pnp.graph.appCatalog.teamsApps.getById(apps[0].id).appDefinitions.getById(appDefinitions[0].id)(); + passed = (def.id === appDefinitions[0].id); + } + return expect(passed).is.true; + })); +});