diff --git a/.funcignore b/.funcignore new file mode 100644 index 0000000..8af9cc6 --- /dev/null +++ b/.funcignore @@ -0,0 +1,21 @@ +.funcignore +*.js.map +*.ts +.git* +.localConfigs +.vscode +local.settings.json +test +tsconfig.json +.DS_Store +.deployment +node_modules/.bin +node_modules/azure-functions-core-tools +README.md +tsconfig.json +teamsapp.yml +teamsapp.*.yml +/env/ +/appPackage/ +/infra/ +/devTools/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8a30d25..2393da7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,398 +1,29 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# TeamsFx files +env/.env.*.user +.DS_Store +build +appPackage/build +.deployment + +# dependencies +/node_modules + +# testing +/coverage + +# Dev tool directories +/devTools/ + +# TypeScript output +dist +out + +# Azure Functions artifacts +bin +obj +appsettings.json +local.settings.json + +# Local data +.localConfigs \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..45da21c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension", + "TeamsDevApp.vscode-adaptive-cards" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..910cce1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,97 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch App in Teams (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://www.office.com/chat?auth=2", + "cascadeTerminateToConfigurations": [ + "Attach to Backend" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen", + "perScriptSourcemaps": "yes" + }, + { + "name": "Launch App in Teams (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://www.office.com/chat?auth=2", + "cascadeTerminateToConfigurations": [ + "Attach to Backend" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen", + "perScriptSourcemaps": "yes" + }, + { + "name": "Preview in Copilot (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://www.office.com/chat?auth=2", + "presentation": { + "group": "remote", + "order": 1 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Preview in Copilot (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://www.office.com/chat?auth=2", + "presentation": { + "group": "remote", + "order": 2 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Attach to Backend", + "type": "node", + "request": "attach", + "port": 9229, + "restart": true, + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Copilot (Edge)", + "configurations": [ + "Launch App in Teams (Edge)", + "Attach to Backend" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { + "group": "all", + "order": 1 + }, + "stopAll": true + }, + { + "name": "Debug in Copilot (Chrome)", + "configurations": [ + "Launch App in Teams (Chrome)", + "Attach to Backend" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { + "group": "all", + "order": 2 + }, + "stopAll": true + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0ed7b2e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "debug.onTaskErrors": "abort", + "json.schemas": [ + { + "fileMatch": [ + "/aad.*.json" + ], + "schema": {} + } + ], + "azureFunctions.stopFuncTaskPostDebug": false, + "azureFunctions.showProjectWarning": false, +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..dbc7dc2 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,129 @@ +// This file is automatically generated by Teams Toolkit. +// The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0. +// See https://aka.ms/teamsfx-tasks for details on how to customize each task. +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Teams App Locally", + "dependsOn": [ + "Validate prerequisites", + "Start local tunnel", + "Create resources", + "Build project", + "Start application" + ], + "dependsOrder": "sequence" + }, + { + "label": "Validate prerequisites", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", + "m365Account", + "portOccupancy" + ], + "portOccupancy": [ + 7071, + 9229 + ] + } + }, + { + // Start the local tunnel service to forward public URL to local port and inspect traffic. + // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions. + "label": "Start local tunnel", + "type": "teamsfx", + "command": "debug-start-local-tunnel", + "args": { + "type": "dev-tunnel", + "ports": [ + { + "portNumber": 7071, + "protocol": "http", + "access": "public", + "writeToEnvironmentFile": { + "endpoint": "OPENAPI_SERVER_URL", // output tunnel endpoint as OPENAPI_SERVER_URL + } + } + ], + "env": "local" + }, + "isBackground": true, + "problemMatcher": "$teamsfx-local-tunnel-watch" + }, + { + "label": "Create resources", + "type": "teamsfx", + "command": "provision", + "args": { + "env": "local" + } + }, + { + "label": "Build project", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "local" + } + }, + { + "label": "Start application", + "dependsOn": [ + "Start backend" + ] + }, + { + "label": "Start backend", + "type": "shell", + "command": "npm run dev:teamsfx", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}", + "env": { + "PATH": "${workspaceFolder}/devTools/func:${env:PATH}" + } + }, + "windows": { + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/func;${env:PATH}" + } + } + }, + "problemMatcher": { + "pattern": { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + }, + "background": { + "activeOnStart": true, + "beginsPattern": "^.*(Job host stopped|signaling restart).*$", + "endsPattern": "^.*(Worker process started and initialized|Host lock lease acquired by instance ID).*$" + } + }, + "presentation": { + "reveal": "silent" + }, + "dependsOn": "Watch backend" + }, + { + "label": "Watch backend", + "type": "shell", + "command": "npm run watch:teamsfx", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": "$tsc-watch", + "presentation": { + "reveal": "silent" + } + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 5cd7cec..bc4f240 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,32 @@ -# Project +# Browse the menu and place an order at a local Italian restaurant using Microsoft 365 Copilot -> This repo has been populated by an initial template to help get you started. Please -> make sure to update the content to build a great experience for community-building. +## Summary -As the maintainer of this project, please make a few updates: +This is a starter project for the **Build your first action for declarative agents with API plugin by using Visual Studio Code** learn module. The module teaches you how to build a declarative agent for Microsoft 365 Copilot that allows you to browse a menu of a local Italian restaurant and place an order. The agent uses an API plugin to connect to an anonymous API. The project contains an Azure Function that serves as the API. -- Improving this README.MD file to provide a great experience -- Updating SUPPORT.MD with content about this project's support experience -- Understanding the security reporting process in SECURITY.MD -- Remove this section from the README +![Declarative agent showing what's on the menu for lunch](./assets/screenshot-menu.png) +![Declarative agent ordering lunch](./assets/screenshot-order.png) + +## Features + +This sample illustrates the following concepts: + +* Building a declarative agent for Microsoft 365 Copilot with an API plugin +* Connecting an API plugin to an anonymous API +* Using [dev tunnels](https://learn.microsoft.com/azure/developer/dev-tunnels/overview) to test the API plugin locally + +## Prerequisites + +* Microsoft 365 tenant with Microsoft 365 Copilot +* [Visual Studio Code](https://code.visualstudio.com/) with the [Teams Toolkit](https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.ms-teams-vscode-extension) extension +* [Node.js v18](https://nodejs.org/en/download/package-manager) +* [Azure Functions Core Tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools) + +## Minimal path to awesome + +* Clone this repository +* Open the Teams Toolkit extension and sign in to your Microsoft 365 tenant with Microsoft 365 Copilot +* Select **Debug in Copilot (Edge)** from the launch configuration dropdown ## Contributing @@ -30,4 +48,4 @@ This project may contain trademarks or logos for projects, products, or services trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. -Any use of third-party trademarks or logos are subject to those third-party's policies. +Any use of third-party trademarks or logos are subject to those third-party's policies. \ No newline at end of file diff --git a/api.http b/api.http new file mode 100644 index 0000000..286f75a --- /dev/null +++ b/api.http @@ -0,0 +1,25 @@ +### Get all dishes +GET http://localhost:7071/api/dishes + +### Get dishes for lunch +GET http://localhost:7071/api/dishes?course=lunch + +### Get drinks for lunch +GET http://localhost:7071/api/dishes?course=lunch&type=drink + +### Place an order +POST http://localhost:7071/api/orders +Content-Type: application/json + +{ + "dishes": [ + { + "name": "frittata", + "quantity": 2 + }, + { + "name": "cappuccino", + "quantity": 1 + } + ] +} diff --git a/appPackage/apiSpecificationFile/ristorante.yml b/appPackage/apiSpecificationFile/ristorante.yml new file mode 100644 index 0000000..052f124 --- /dev/null +++ b/appPackage/apiSpecificationFile/ristorante.yml @@ -0,0 +1,128 @@ +openapi: 3.0.0 +info: + title: Il Ristorante menu API + version: 1.0.0 + description: API to retrieve dishes and place orders for Il Ristorante. +servers: + - url: http://localhost:7071/api + description: Il Ristorante API server +paths: + /dishes: + get: + operationId: getDishes + summary: Get all available dishes + description: Retrieve a list of all dishes, optionally filtered by course and allergens. + parameters: + - in: query + name: course + schema: + type: string + description: Filter dishes by course. Can be breakfast, lunch, or dinner. + - in: query + name: name + schema: + type: string + description: Find dishes by name. + - in: query + name: type + schema: + type: string + description: Filter dishes by type. Can be dish or drink. + - in: query + name: allergens + schema: + type: array + items: + type: string + description: Filter dishes to exclude specific allergens. + responses: + '200': + description: A list of dishes. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Dish' + + /orders: + post: + operationId: placeOrder + summary: Place an order + description: Place an order by providing an array of dishes and quantities. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + dishes: + type: array + description: List of items to order. Each item consists of a dish and a quantity. + items: + type: object + properties: + name: + type: string + description: The name of the dish to order. + quantity: + type: integer + description: The quantity of the dish to order. + required: + - name + - quantity + required: + - dishes + responses: + '201': + description: Order placed successfully. + content: + application/json: + schema: + type: object + properties: + order_id: + type: integer + status: + type: string + example: confirmed + total_price: + type: number + format: float + '400': + description: Invalid order request. + +components: + schemas: + Dish: + type: object + properties: + id: + type: integer + description: Unique identifier for the dish. + name: + type: string + description: The name of the dish. + description: + type: string + description: A short description of the dish. + image_url: + type: string + description: URL to an image of the dish. + price: + type: number + format: float + description: The price of the dish. + allergens: + type: array + items: + type: string + description: List of allergens in the dish, if any. + type: + type: string + description: The type of dish. Can be either a dish or a drink. + course: + type: string + description: The course to which the dish belongs. Can be breakfast, lunch, or dinner. + diff --git a/appPackage/color.png b/appPackage/color.png new file mode 100644 index 0000000..11e255f Binary files /dev/null and b/appPackage/color.png differ diff --git a/appPackage/declarativeAgent.json b/appPackage/declarativeAgent.json new file mode 100644 index 0000000..7addb11 --- /dev/null +++ b/appPackage/declarativeAgent.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/json-schemas/copilot/declarative-agent/v1.0/schema.json", + "version": "v1.0", + "name": "Declarative agent", + "description": "Declarative agent created with Teams Toolkit", + "instructions": "$[file('instruction.txt')]" +} \ No newline at end of file diff --git a/appPackage/instruction.txt b/appPackage/instruction.txt new file mode 100644 index 0000000..ffa8139 --- /dev/null +++ b/appPackage/instruction.txt @@ -0,0 +1 @@ +You are a declarative agent and were created with Team Toolkit. You should start every response and answer to the user with "Thanks for using Teams Toolkit to create your declarative agent!\n\n" and then answer the questions and help the user. \ No newline at end of file diff --git a/appPackage/manifest.json b/appPackage/manifest.json new file mode 100644 index 0000000..af12709 --- /dev/null +++ b/appPackage/manifest.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.19/MicrosoftTeams.schema.json", + "manifestVersion": "1.19", + "id": "${{TEAMS_APP_ID}}", + "version": "1.0.0", + "developer": { + "name": "Teams App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com/privacy", + "termsOfUseUrl": "https://www.example.com/termsofuse" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "da-default${{APP_NAME_SUFFIX}}", + "full": "Full name for da-default" + }, + "description": { + "short": "Short description for da-default", + "full": "Full description for da-default" + }, + "accentColor": "#FFFFFF", + "copilotAgents": { + "declarativeAgents": [ + { + "id": "declarativeAgent", + "file": "declarativeAgent.json" + } + ] + }, + "permissions": [ + "identity", + "messageTeamMembers" + ] +} \ No newline at end of file diff --git a/appPackage/outline.png b/appPackage/outline.png new file mode 100644 index 0000000..f7a4c86 Binary files /dev/null and b/appPackage/outline.png differ diff --git a/assets/screenshot-menu.png b/assets/screenshot-menu.png new file mode 100644 index 0000000..b8d0c1b Binary files /dev/null and b/assets/screenshot-menu.png differ diff --git a/assets/screenshot-order.png b/assets/screenshot-order.png new file mode 100644 index 0000000..e06a4af Binary files /dev/null and b/assets/screenshot-order.png differ diff --git a/env/.env.dev b/env/.env.dev new file mode 100644 index 0000000..342a8af --- /dev/null +++ b/env/.env.dev @@ -0,0 +1,17 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +TEAMS_APP_ID= +TEAMS_APP_PUBLISHED_APP_ID= +TEAMS_APP_TENANT_ID= +API_FUNCTION_ENDPOINT= +API_FUNCTION_RESOURCE_ID= \ No newline at end of file diff --git a/env/.env.local b/env/.env.local new file mode 100644 index 0000000..c33ac4f --- /dev/null +++ b/env/.env.local @@ -0,0 +1,7 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=local +APP_NAME_SUFFIX=local + +# Generated during provision, you can also add your own variables. diff --git a/host.json b/host.json new file mode 100644 index 0000000..06d01bd --- /dev/null +++ b/host.json @@ -0,0 +1,15 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + }, + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[4.*, 5.0.0)" + } +} diff --git a/infra/azure.bicep b/infra/azure.bicep new file mode 100644 index 0000000..8021c1d --- /dev/null +++ b/infra/azure.bicep @@ -0,0 +1,57 @@ +@maxLength(20) +@minLength(4) +param resourceBaseName string +param functionAppSKU string + +param location string = resourceGroup().location +param serverfarmsName string = resourceBaseName +param functionAppName string = resourceBaseName + +// Compute resources for Azure Functions +resource serverfarms 'Microsoft.Web/serverfarms@2021-02-01' = { + name: serverfarmsName + location: location + sku: { + name: functionAppSKU // You can follow https://aka.ms/teamsfx-bicep-add-param-tutorial to add functionServerfarmsSku property to provisionParameters to override the default value "Y1". + } + properties: {} +} + +// Azure Functions that hosts your function code +resource functionApp 'Microsoft.Web/sites@2021-02-01' = { + name: functionAppName + kind: 'functionapp' + location: location + properties: { + serverFarmId: serverfarms.id + httpsOnly: true + siteConfig: { + appSettings: [ + { + name: 'FUNCTIONS_EXTENSION_VERSION' + value: '~4' // Use Azure Functions runtime v4 + } + { + name: 'FUNCTIONS_WORKER_RUNTIME' + value: 'node' // Set runtime to NodeJS + } + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' // Run Azure Functions from a package file + } + { + name: 'WEBSITE_NODE_DEFAULT_VERSION' + value: '~18' // Set NodeJS version to 18.x + } + ] + ftpsState: 'FtpsOnly' + } + } +} +var apiEndpoint = 'https://${functionApp.properties.defaultHostName}' + + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output API_FUNCTION_ENDPOINT string = apiEndpoint +output API_FUNCTION_RESOURCE_ID string = functionApp.id +output OPENAPI_SERVER_URL string = apiEndpoint diff --git a/infra/azure.parameters.json b/infra/azure.parameters.json new file mode 100644 index 0000000..ede6521 --- /dev/null +++ b/infra/azure.parameters.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "plugin${{RESOURCE_SUFFIX}}" + }, + "functionAppSKU": { + "value": "Y1" + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..afdfd9f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,183 @@ +{ + "name": "daristoranteapi", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "daristoranteapi", + "version": "1.0.0", + "dependencies": { + "@azure/functions": "^4.3.0" + }, + "devDependencies": { + "@types/node": "^18.11.9", + "env-cmd": "^10.1.0", + "typescript": "^4.1.6" + } + }, + "node_modules/@azure/functions": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@azure/functions/-/functions-4.5.1.tgz", + "integrity": "sha512-ikiw1IrM2W9NlQM3XazcX+4Sq3XAjZi4eeG22B5InKC2x5i7MatGF2S/Gn1ACZ+fEInwu+Ru9J8DlnBv1/hIvg==", + "dependencies": { + "cookie": "^0.6.0", + "long": "^4.0.0", + "undici": "^5.13.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/node": { + "version": "18.19.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.55.tgz", + "integrity": "sha512-zzw5Vw52205Zr/nmErSEkN5FLqXPuKX/k5d1D7RKHATGqU7y6YfX9QxZraUzUrFGqH6XzOzG196BC35ltJC4Cw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/env-cmd": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz", + "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", + "dev": true, + "dependencies": { + "commander": "^4.0.0", + "cross-spawn": "^7.0.0" + }, + "bin": { + "env-cmd": "bin/env-cmd.js" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..802f349 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "daristoranteapi", + "version": "1.0.0", + "scripts": { + "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", + "dev": "func start --typescript --language-worker=\"--inspect=9229\" --port \"7071\" --cors \"*\"", + "build": "tsc", + "watch:teamsfx": "tsc --watch", + "watch": "tsc -w", + "prestart": "npm run build", + "start": "func start", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "@azure/functions": "^4.3.0" + }, + "devDependencies": { + "env-cmd": "^10.1.0", + "@types/node": "^18.11.9", + "typescript": "^4.1.6" + }, + "main": "dist/src/functions/*.js" +} diff --git a/src/data.json b/src/data.json new file mode 100644 index 0000000..15645f6 --- /dev/null +++ b/src/data.json @@ -0,0 +1,176 @@ +[ + { + "id": 1, + "name": "Classic Italian Frittata", + "description": "A fluffy omelette filled with sautéed mushrooms, onions, and melted pecorino, served with a side of roasted cherry tomatoes.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/frittata.jpeg", + "price": 8.99, + "allergens": [ + "eggs", + "dairy" + ], + "course": "breakfast", + "type": "dish" + }, + { + "id": 2, + "name": "Prosciutto & Melon", + "description": "Fresh, sweet cantaloupe wrapped in thin slices of salty prosciutto, drizzled with balsamic glaze and a hint of mint.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/prosciutto_melon.jpeg", + "price": 7.50, + "allergens": [], + "course": "breakfast", + "type": "dish" + }, + { + "id": 3, + "name": "Ricotta Pancakes", + "description": "Light and fluffy pancakes made with creamy ricotta cheese, topped with fresh berries and a dusting of powdered sugar.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/ricotta_pancakes.jpeg", + "price": 9.99, + "allergens": [ + "gluten", + "dairy", + "eggs" + ], + "course": "breakfast", + "type": "dish" + }, + { + "id": 10, + "name": "Cappuccino", + "description": "A perfect blend of espresso and steamed milk with a thick, velvety foam topping.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/cappuccino.jpeg", + "price": 3.99, + "allergens": [ + "dairy" + ], + "course": "breakfast", + "type": "drink" + }, + { + "id": 11, + "name": "Freshly Squeezed Orange Juice", + "description": "Refreshing and tangy, made from hand-picked oranges, a perfect start to your day.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/orange_juice.jpeg", + "price": 4.50, + "allergens": [], + "course": "breakfast", + "type": "drink" + }, + { + "id": 4, + "name": "Caprese Salad", + "description": "Juicy vine-ripened tomatoes, fresh mozzarella, and fragrant basil leaves, drizzled with extra virgin olive oil and a touch of balsamic.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/caprese_salad.jpeg", + "price": 10.50, + "allergens": [ + "dairy" + ], + "course": "lunch", + "type": "dish" + }, + { + "id": 5, + "name": "Spaghetti Carbonara", + "description": "Al dente spaghetti tossed in a velvety sauce of eggs, Parmesan, and crispy pancetta, finished with cracked black pepper and a sprinkle of parsley.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/spaghetti_carbonara.jpeg", + "price": 14.99, + "allergens": [ + "gluten", + "dairy", + "eggs" + ], + "course": "lunch", + "type": "dish" + }, + { + "id": 6, + "name": "Grilled Chicken Panini", + "description": "Grilled marinated chicken breast with sun-dried tomatoes, arugula, and melted provolone, served on toasted ciabatta bread with pesto mayo.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/chicken_panini.jpeg", + "price": 12.50, + "allergens": [ + "gluten", + "dairy" + ], + "course": "lunch", + "type": "dish" + }, + { + "id": 12, + "name": "Iced Tea", + "description": "A cool and refreshing brewed tea with a hint of lemon and mint.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/iced_tea.jpeg", + "price": 2.99, + "allergens": [], + "course": "lunch", + "type": "drink" + }, + { + "id": 13, + "name": "San Pellegrino Sparkling Water", + "description": "Crisp, bubbly sparkling water from the Italian Alps, a refreshing palate cleanser.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/sparkling_water.jpeg", + "price": 3.50, + "allergens": [], + "course": "lunch", + "type": "drink" + }, + { + "id": 7, + "name": "Veal Osso Buco", + "description": "Tender veal shanks braised in a rich tomato and white wine sauce, served with a creamy saffron risotto and gremolata.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/veal_osso_buco.jpeg", + "price": 23.99, + "allergens": [], + "course": "dinner", + "type": "dish" + }, + { + "id": 8, + "name": "Seafood Linguine", + "description": "Fresh linguine pasta tossed with mussels, clams, shrimp, and calamari in a delicate white wine and garlic sauce, with a hint of red pepper flakes.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/seafood_linguine.jpeg", + "price": 19.99, + "allergens": [ + "gluten", + "shellfish" + ], + "course": "dinner", + "type": "dish" + }, + { + "id": 9, + "name": "Tiramisu", + "description": "A luscious, creamy blend of espresso-soaked ladyfingers, mascarpone, and cocoa, this dessert is a classic Italian indulgence.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/tiramisu.jpeg", + "price": 7.50, + "allergens": [ + "dairy", + "eggs" + ], + "course": "dinner", + "type": "dish" + }, + { + "id": 14, + "name": "Chianti Classico", + "description": "A robust and velvety red wine with notes of cherries, violet, and subtle spice, perfect for pairing with hearty Italian dishes.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/chianti_classico.jpeg", + "price": 9.99, + "allergens": [], + "course": "dinner", + "type": "drink" + }, + { + "id": 15, + "name": "Espresso", + "description": "A bold and intense shot of rich espresso, with deep crema, the perfect end to a delightful meal.", + "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api/assets/espresso.jpeg", + "price": 2.99, + "allergens": [], + "course": "dinner", + "type": "drink" + } +] \ No newline at end of file diff --git a/src/functions/dishes.ts b/src/functions/dishes.ts new file mode 100644 index 0000000..f70621d --- /dev/null +++ b/src/functions/dishes.ts @@ -0,0 +1,53 @@ +/* This code sample provides a starter kit to implement server side logic for your Teams App in TypeScript, + * refer to https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference for complete Azure Functions + * developer guide. + */ + +import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; + +import data from "../data.json"; + +export async function dishes( + req: HttpRequest, + context: InvocationContext +): Promise { +const course = req.query.get('course'); +const allergensString = req.query.get('allergens'); +const allergens: string[] = allergensString ? allergensString.split(",") : []; +const type = req.query.get('type'); +const name = req.query.get('name'); + + // clone so that we're not modifying the original data + let filteredDishes = [...data]; + + if (name) { + filteredDishes = filteredDishes.filter(dish => dish.name.toLowerCase().includes(name.toLowerCase())); + } + + if (course) { + filteredDishes = filteredDishes.filter(dish => dish.course === course); + } + + if (type) { + filteredDishes = filteredDishes.filter(dish => dish.type === type); + } + + if (allergens.length > 0) { + filteredDishes = filteredDishes.filter((dish) => + allergens.every(allergen => !dish.allergens.includes(allergen)) + ); + } + + return { + status: 200, + jsonBody: { + dishes: filteredDishes + }, + } as HttpResponseInit; +} + +app.http("dishes", { + methods: ["GET"], + authLevel: "anonymous", + handler: dishes, +}); diff --git a/src/functions/placeOrder.ts b/src/functions/placeOrder.ts new file mode 100644 index 0000000..44bd54c --- /dev/null +++ b/src/functions/placeOrder.ts @@ -0,0 +1,81 @@ +/* This code sample provides a starter kit to implement server side logic for your Teams App in TypeScript, + * refer to https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference for complete Azure Functions + * developer guide. + */ + +import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; + +import data from "../data.json"; + +interface OrderedDish { + name?: string; + quantity?: number; +} +interface Order { + dishes: OrderedDish[]; +} + +export async function placeOrder( + req: HttpRequest, + context: InvocationContext +): Promise { + let order: Order | undefined; + try { + order = await req.json() as Order | undefined; + } + catch (error) { + return { + status: 400, + jsonBody: { message: "Invalid JSON format" }, + } as HttpResponseInit; + } + + if (!order.dishes || !Array.isArray(order.dishes)) { + return { + status: 400, + jsonBody: { message: "Invalid order format" }, + } as HttpResponseInit; + } + + let totalPrice = 0; + const orderDetails = order.dishes.map(orderedDish => { + const dish = data.find(d => d.name.toLowerCase().includes(orderedDish.name.toLowerCase())); + if (dish) { + totalPrice += dish.price * orderedDish.quantity; + return { + name: dish.name, + quantity: orderedDish.quantity, + price: dish.price, + }; + } + else { + context.error(`Invalid dish: ${orderedDish.name}`); + return null; + } + }); + + if (orderDetails.includes(null)) { + return { + status: 400, + jsonBody: { message: "One or more invalid dishes" }, + } as HttpResponseInit; + } + + // Simulate order placement (e.g., saving to database) + const orderId = Math.floor(Math.random() * 10000); + + return { + status: 201, + jsonBody: { + order_id: orderId, + status: "confirmed", + total_price: totalPrice, + }, + } as HttpResponseInit; +} + +app.http("orders", { + methods: ["POST"], + authLevel: "anonymous", + handler: placeOrder, +}); diff --git a/teamsapp.local.yml b/teamsapp.local.yml new file mode 100644 index 0000000..c2da493 --- /dev/null +++ b/teamsapp.local.yml @@ -0,0 +1,81 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.7/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.7 + +provision: + - uses: file/createOrUpdateJsonFile + with: + target: ./local.settings.json + content: + IsEncrypted: false + Values: + FUNCTIONS_WORKER_RUNTIME: 'node' + + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: da-ristorante-api${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Set required variables for local launch + - uses: script + with: + run: + echo "::set-teamsfx-env FUNC_NAME=repair"; + echo "::set-teamsfx-env FUNC_ENDPOINT=http://localhost:7071"; + + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputFolder: ./appPackage/build + + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Extend your Teams app to Outlook and the Microsoft 365 app + - uses: teamsApp/extendToM365 + with: + # Relative path to the build app package. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + titleId: M365_TITLE_ID + appId: M365_APP_ID + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + func: + version: ~4.0.5530 + symlinkDir: ./devTools/func + # Write the information of installed development tool(s) into environment + # file for the specified environment variable(s). + writeToEnvironmentFile: + funcPath: FUNC_PATH + + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install --no-audit diff --git a/teamsapp.yml b/teamsapp.yml new file mode 100644 index 0000000..b369b99 --- /dev/null +++ b/teamsapp.yml @@ -0,0 +1,135 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.7/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.7 + +environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: da-ristorante-api${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-sme + # Teams Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputFolder: ./appPackage/build + + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Extend your Teams app to Outlook and the Microsoft 365 app + - uses: teamsApp/extendToM365 + with: + # Relative path to the build app package. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + titleId: M365_TITLE_ID + appId: M365_APP_ID + +# Triggered when 'teamsapp deploy' is executed +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install + + - uses: cli/runNpmCommand + name: build app + with: + args: run build --if-present + + # Deploy your application to Azure Functions using the zip deploy feature. + # For additional details, see at https://aka.ms/zip-deploy-to-azure-functions + - uses: azureFunctions/zipDeploy + with: + # deploy base folder + artifactFolder: . + # Ignore file location, leave blank will ignore nothing + ignoreFile: .funcignore + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{API_FUNCTION_RESOURCE_ID}} + +# Triggered when 'teamsapp publish' is executed +publish: + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputFolder: ./appPackage/build + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Publish the app to + # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps) + # for review and approval + - uses: teamsApp/publishAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + publishedAppId: TEAMS_APP_PUBLISHED_APP_ID diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..716af80 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "lib": ["ES2016"], + "outDir": "dist", + "rootDir": ".", + "sourceMap": true, + "strict": false, + "resolveJsonModule": true, + "esModuleInterop": true, + "typeRoots": ["./node_modules/@types"] + } +} \ No newline at end of file