From c016f263343c4cbb2ee08d0b35078e911d0a65c2 Mon Sep 17 00:00:00 2001 From: Horea Porutiu Date: Mon, 9 Oct 2023 15:37:06 +0200 Subject: [PATCH] Fix GitHub appcards (#203) * adding await otherwise the card is never updated add in database config screenshots * improve README * adding sample env --------- Co-authored-by: Mettin Parzinski --- examples/github-appcards/.sample.env | 37 +++++ examples/github-appcards/README.md | 154 ++++++++++++++---- .../netlify/functions/issues.js | 50 +++--- .../netlify/functions/project-cards.js | 58 +++---- 4 files changed, 208 insertions(+), 91 deletions(-) create mode 100644 examples/github-appcards/.sample.env diff --git a/examples/github-appcards/.sample.env b/examples/github-appcards/.sample.env new file mode 100644 index 000000000..21ad4b72f --- /dev/null +++ b/examples/github-appcards/.sample.env @@ -0,0 +1,37 @@ +# Generate an access token in GitHub, and enter the value here. +# To generate the access token, go to https://github.com/settings/tokens +VITE_GH_ACCESS_TOKEN= + +# In Supabase, you create a Postgres database, and you assign it a password. +# Enter the password of the created database in Supabase here. +# For more information, see: +# https://supabase.com/docs/guides/database/managing-passwords +VITE_SUPABASE_PASSWORD= + +# Enter the URL of the Supabase database that the app uses for data persistence here. +# For more information, see: +# https://supabase.com/docs/guides/database +# Login to your account, then go down to Project Settings -> API -> URL. +# Your connection should look like `https://ahnvcdiskdadfgbljsdm.supabase.co +VITE_DATABASE_URL= + +# Enter the API key of the Supabase project with the database that the app uses for data persistence here. +# For more information, see: +# Login to your account, then go down to Project Settings -> API -> Project API keys -> anon public. +VITE_DATABASE_PUBLIC_KEY= + +# Enter the base URL of the hosting service your app runs on here. +# If you're developing locally, it can be 'localhost'. +VITE_BASE_URL= + +# Enter the client secret of your app here. +# To retrieve the client secret, go to https://miro.com/app/settings/user-profile/ +# Select 'Your apps', and then the app whose secret you want to retrieve. +# The app client secret is under 'App Credentials' > 'Client secret'. +MIRO_CLIENT_SECRET= + +# Enter the OAuth code grant flow redirect URI for your app here. +# For more information, see: +# https://developers.miro.com/docs/getting-started-with-oauth +# https://developers.miro.com/reference/authorization-flow-for-expiring-access-tokens +MIRO_REDIRECT_URI= \ No newline at end of file diff --git a/examples/github-appcards/README.md b/examples/github-appcards/README.md index 54df557e5..a6c6aad6a 100644 --- a/examples/github-appcards/README.md +++ b/examples/github-appcards/README.md @@ -2,8 +2,15 @@ This full-stack example shows how to build an integration with GitHub that syncs data between GitHub issues and Miro app cards. -⚠️ Only deprecated "Classic" GitHub projects work with this app. This means that you cannot connect this app to your own repo unless you have a deprecated "Classic" GitHub project. This is why all of the issues are fetched / stored in this -[public Miro classic GitHub project](https://github.com/bishopwm/github-cards/projects/1). ⚠️ +🚨 🚨 🚨 +Only deprecated "Classic" GitHub projects work with this app. This means that you cannot connect this app to your own repo unless you +have a deprecated "Classic" GitHub project. This is why all of the issues are fetched / stored in this +[public Miro classic GitHub project](https://github.com/bishopwm/github-cards/projects/1). + +This app is meant to show the basic concepts behind 2-way sync, but is in no way a working solution. This is intended for simple demo purposes to show a simple 2-way sync with free services like Netlify, Supabase, and Miro. + +This app involves setting up different tools such as Netlify for deploying your app, Supabase for storing access tokens and +GitHub for using GitHub actions to detech any changes in your projects. It takes 30-40 minutes to configure. # 👨🏻‍💻 App Demo @@ -15,6 +22,9 @@ https://github.com/miroapp/app-examples/assets/10428517/e2c7b34a-e97d-453e-b64b- - [Tools and Technologies](#tools) - [Prerequisites](#prerequisites) - [Associated Developer Tutorial](#tutorial) +- [Database Configuration](#database) +- [Netlify Configuration](#netlify) +- [Miro App Configuration](#miro) - [Run the app locally](#run) - [Folder Structure](#folder) - [Contributing](#contributing) @@ -34,6 +44,7 @@ https://github.com/miroapp/app-examples/assets/10428517/e2c7b34a-e97d-453e-b64b- - [React](https://react.dev/) - [Netlify](https://www.netlify.com/) +- [Supabase](https://supabase.com/) - [Vite](https://vitejs.dev/) # ✅ Prerequisites @@ -44,15 +55,110 @@ https://github.com/miroapp/app-examples/assets/10428517/e2c7b34a-e97d-453e-b64b- - Your development environment includes [Node.js 14.13](https://nodejs.org/en/download) or a later version. - GitHub Account and [access token](https://github.com/settings/tokens). - [Supabase account](https://supabase.com/) and database. +- [Netlify account](https://www.netlify.com/). # 📖 Associated Developer Tutorial > To view a more in depth developer tutorial > of this app (including code explanations) see the [GitHub app cards 2-way sync tutorial](https://developers.miro.com/docs/enable-2-way-sync-between-app-cards-and-github-cards) on Miro's Developer documentation. -# 🏃🏽‍♂️ Run the app locally +# Database Configuration + +1. Create a database in Supabase. First you may need to create an Org. +2. Go into your dashboard and into your project and to the `table editor`. +3. Click on `Create a new table` +4. Name this table `auth` and add in the following columns, with the respective `Format` as shown in the screenshot below. Note that capitalization is important for the table name. This table will hold access_tokens to be able to call the Miro REST API to sync changes which happen in the GitHub project. Disable Row Level security. + +![auth-database-configuration](https://github.com/miroapp/app-examples/assets/10428517/bbbcefcf-0621-4f6f-812d-021ae4aab047) + +6. Click on `Create a new table` and name this table `card-mapping` and add in the following columns, with the respective `Format` as shown in the screenshot below. This table will hold the app card ID from Miro and the GitHub issue ID along with the MiroUserId. + +![card-mapping-database-configuration](https://github.com/miroapp/app-examples/assets/10428517/8eadeeb0-ab7a-4239-9961-c3dfcc038d1f) + +8. Click on `Edit column` for the `miroUserId` in the `card-mapping` table, and then add in the following `Foreign Key Relation` as shown in the screenshot below. + +supabase-foreign-key-config-2 + +7. Once you save the `Foreign Key Relation` your `miroUserId` from the `card-mapping` table should look something like the screenshot below. Click `Save`. We need this to be able to associate the miroUserId with a access_token so we can invoke the Miro REST API. + +![supabase-foreign-key-config-summary](https://github.com/miroapp/app-examples/assets/10428517/3670da24-12be-4302-853f-0f24d45063b9) + +# ☁️ Netlify Configuration + +The code in this repo contains three different functions which are meant to be serverless functions: + +- netlify/functions/[authorize.js](netlify/functions/authorize.js) +- netlify/functions/[issues.js](netlify/functions/issues.js) +- netlify/functions/[project-cards.js](netlify/functions/project-cards.js) + +[authorize.js](netlify/functions/authorize.js) is going to run when you share your app with someone and then go through the OAuth flow. + +[issues.js](netlify/functions/issues.js) is going to run when you update the title or description of an issue. We have a GitHub action which will do this, defined in `.github/workflows/issues.yml`. You will need to update the [GitHub Action URL](https://github.com/miroapp/app-examples/blob/main/examples/github-appcards/.github/workflows/issues.yml#L11) to point to your deployed Netlify function. It should look something like `https://miro-github.netlify.app/.netlify/functions/issues`. + +[project-cards.js](netlify/functions/project-cards.js) is going to run when you move a card to a different column i.e. if you move a card from `To Do` to `Done`. We have a GitHub action which will do this, defined in `.github/workflows/project-cards.yml`. You will need to update the [GitHub Action URL](https://github.com/miroapp/app-examples/blob/main/examples/github-appcards/.github/workflows/project-cards.yml#L13) to point to your deployed Netlify function. It should look something like `https://miro-github.netlify.app/.netlify/functions/project-cards`. + +Now we will show you step by step how to set this up for free with Netlify. When you deploy your site with Netlify, it will +automatically generate those functions since Netlify is looking for serverless functions in the `netlify/functions` directory. + +1. Go to your Netlify account and auth with your GitHub account. +2. Download the `github-appcards` repo by going to developers.miro.com and then scrolling down to `Create apps using samples`. Then find GitHub App Cards and click on the `Download source code as .zip`. Unzip the files. + ![download-source-code](https://github.com/miroapp/app-examples/assets/10428517/42edc852-3c2d-4f1d-bce7-a2ec4d7c7cb5) + +3. Create a new GitHub repo, and push up your `github-appcards` project which you just downloaded to it. You can use the following commands to do so: + +``` +cd github-appcards +git init +git add . +git commit -m "first commit" +git remote add origin https://github.com// +git push -u origin main +``` + +4. At this point, you should have a personal repo which has the [GitHub-appcards code](https://github.com/miroapp/app-examples/tree/main/examples/github-appcards) in it. We will use this repo as a way to + deploy this app with Netlify. + +5. Go into Netlify, login, and from the `Team overview` section, click on `Add new site` -> `Import an existing project` -> `Deploy with GitHub` and then + authenticate into GitHub and select this project which you just created which holds the GitHub app cards code. Select the `main` branch and `Deploy` + +6. Once the deploy is complete, you app should be deployed to `.netlify.app`. For example, mine was `https://peaceful-fairy-c2e727.netlify.app` + as shown in the screenshot below. I will use the `https://peaceful-fairy-c2e727.netlify.app` as the example for how to connect your Miro app to the + Netlify app but just understand that your URL will be different. -1. Create a `.env` file and add in the associated env variables as documented below: + netlify-app-name + +# ☁️ Miro App Configuration + +1. Now, let's create a new Miro app on `developer.miro.com`. +2. Once you've created the app, from the app setting page, click on `edit in Manifest` and paste in the following: + +```yaml +appName: GitHub App Cards +sdkVersion: SDK_V2 +sdkUri: https://peaceful-fairy-c2e727.netlify.app +boardPicker: + allowedDomains: [] +redirectUris: + - https://peaceful-fairy-c2e727.netlify.app/.netlify/functions/authorize +redirectUriForSdk: https://peaceful-fairy-c2e727.netlify.app/.netlify/functions/authorize +scopes: + - boards:write + - boards:read +icons: + colored: "" + outline: "" +``` + + Make sure to change `sdkUri`, `redirectUris`, and `redirectUriForSdk` with your site name. + +3. From the `Redirect URI for OAuth2.0` section in app settings, click on `Options` and make sure `Use this URI for SDK authorization` is checked, + as shown in the screenshot below. + +use-redirect-url-for-sdk-auth + +5. Rename the `.sample.env` file at the base of the `github-appcards` repo to `.env` and fill in the values as detailed in the comments. Note, + your `redirectUriForSdk` on your app settings should be the same as the `MIRO_REDIRECT_URI` in your env variables. Below, I have used the + `peaceful-fairy` example to show how you should fill in this env file. Note that you must use your own deployed app base name and then it should end in `.netlify/functions/authorize` for the `MIRO_REDIRECT_URI`. ```.env # Generate an access token in GitHub, and enter the value here. @@ -77,7 +183,7 @@ VITE_DATABASE_PUBLIC_KEY= # Enter the base URL of the hosting service your app runs on here. # If you're developing locally, it can be 'localhost'. -VITE_BASE_URL= +VITE_BASE_URL=https://peaceful-fairy-c2e727.netlify.app/ # Enter the client secret of your app here. # To retrieve the client secret, go to https://miro.com/app/settings/user-profile/ @@ -89,38 +195,26 @@ MIRO_CLIENT_SECRET= # For more information, see: # https://developers.miro.com/docs/getting-started-with-oauth # https://developers.miro.com/reference/authorization-flow-for-expiring-access-tokens -MIRO_REDIRECT_URI= +MIRO_REDIRECT_URI=https://peaceful-fairy-c2e727.netlify.app/.netlify/functions/authorize ``` -2. Run `npm install` to install dependencies. -3. Run `npm start` to start developing. \ - Your URL should be similar to this example: - ``` - http://localhost:3000 - ``` -4. Open the [app manifest editor](https://developers.miro.com/docs/manually-create-an-app#step-2-configure-your-app-in-miro) by clicking **Edit in Manifest**. \ - In the app manifest editor, configure the app as follows and then `click save`. +6. Once you have filled this in, go back to your Netlify deploys and click on `Site configuration` -> then click on `Environment variables` -> + `Add a variable` -> `import from a .env file` and then just copy and paste the content and click on `Import variables`. -```yaml -# See https://developers.miro.com/docs/app-manifest on how to use this -appName: GitHub App Cards -sdkUri: "http://localhost:3000" -redirectUris: - - http://localhost:3000/authorize -scopes: - - boards:read - - boards:write -``` +7. Trigger a new deploy. -5. Go back to your app home page, and under the `Permissions` section, you will see a blue button that says `Install app and get OAuth token`. Click that button. Then click on `Add` as shown in the video below. In the video we install a different app, but the process is the same regardless of the app. +Make sure your URLs have https:// at the beginning, otherwise the OAuth flow will not work. +Also make sure that your MIRO_REDIRECT_URI ends in `.netlify/functions/authorize`. -> ⚠️ We recommend to install your app on a [developer team](https://developers.miro.com/docs/create-a-developer-team) while you are developing or testing apps.⚠️ +# 🏃🏽‍♂️ Run the app + +1. Go back to your app home page, and under the `Share app` section, click on `Copy` and paste the URL in your browser. Install the app on a dev team. -https://github.com/miroapp/app-examples/assets/10428517/1e6862de-8617-46ef-b265-97ff1cbfe8bf +> ⚠️ We recommend to install your app on a [developer team](https://developers.miro.com/docs/create-a-developer-team) while you are developing or testing apps.⚠️ -6. Go to your developer team, and open your boards. -7. Click on the plus icon from the bottom section of your left sidebar. If you hover over it, it will say `More apps`. -8. Search for your app `GitHub App Cards` or whatever you chose to name it. Click on your app to use it, as shown in the video below. In the video we search for a different app, but the process is the same regardless of the app. +2. Go to your developer team, and open your boards. +3. Click on the plus icon from the bottom section of your left sidebar. If you hover over it, it will say `More apps`. +4. Search for your app `GitHub App Cards` or whatever you chose to name it. Click on your app to use it, as shown in the video below. In the video we search for a different app, but the process is the same regardless of the app. https://github.com/horeaporutiu/app-examples-template/assets/10428517/b23d9c4c-e785-43f9-a72e-fa5d82c7b019 diff --git a/examples/github-appcards/netlify/functions/issues.js b/examples/github-appcards/netlify/functions/issues.js index aa870bffe..add1ad9bb 100644 --- a/examples/github-appcards/netlify/functions/issues.js +++ b/examples/github-appcards/netlify/functions/issues.js @@ -66,34 +66,32 @@ exports.handler = async function (event) { }), }; - return new Promise((resolve) => { - try { - const miroAppCardResponse = fetch( - `https://api.miro.com/v2/boards/${item.miroBoardId}/app_cards/${item.miroAppCardId}`, - options, - ); + try { + const miroAppCardResponse = await fetch( + `https://api.miro.com/v2/boards/${item.miroBoardId}/app_cards/${item.miroAppCardId}`, + options, + ); - if (miroAppCardResponse.ok) { - const data = miroAppCardResponse.json(); - const response = { - statusCode: 200, - headers: { "content-type": "application/json" }, - body: JSON.stringify(data), - }; - resolve(response); - } else { - resolve({ - statusCode: miroAppCardResponse.status || 500, - body: miroAppCardResponse.statusText, - }); - } - } catch (error) { - resolve({ - statusCode: error.statusCode || 500, - body: error.message, - }); + if (miroAppCardResponse.ok) { + const data = await miroAppCardResponse.json(); + const response = { + statusCode: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify(data), + }; + return response; + } else { + return { + statusCode: miroAppCardResponse.status || 500, + body: miroAppCardResponse.statusText, + }; } - }); + } catch (error) { + return { + statusCode: error.statusCode || 500, + body: error.message, + }; + } }), ); } diff --git a/examples/github-appcards/netlify/functions/project-cards.js b/examples/github-appcards/netlify/functions/project-cards.js index f52c88088..38056c31a 100644 --- a/examples/github-appcards/netlify/functions/project-cards.js +++ b/examples/github-appcards/netlify/functions/project-cards.js @@ -5,7 +5,6 @@ **/ import { createClient } from "@supabase/supabase-js"; import fetch from "node-fetch"; -import username from "../../src/constants.ts"; const supabase = createClient( process.env.VITE_DATABASE_URL, @@ -24,10 +23,8 @@ exports.handler = async function (event) { const body = JSON.parse(event.body); const gitHubProjectCard = body.gitHubProjectCard; const gitHubProjectColumnId = gitHubProjectCard.column_id; - const gitHubIssueNumer = gitHubProjectCard.content_url - .split( - "https:// api.github.com/repos/" + username + "/github-cards/issues/", - ) + const gitHubIssueNumber = gitHubProjectCard.content_url + .split("https:// api.github.com/repos/bishopwm/github-cards/issues/") .pop(); const headers = { @@ -57,7 +54,7 @@ exports.handler = async function (event) { .select( "id, miroAppCardId::text, gitHubIssueId, miroUserId::text, gitHubUsername, created_at, miroBoardId, gitHubIssueNumber, auth ( access_token )", ) - .eq("gitHubIssueNumber", gitHubIssueNumer); + .eq("gitHubIssueNumber", gitHubIssueNumber); // No Miro App Card Found if (error) { @@ -105,42 +102,33 @@ exports.handler = async function (event) { }), }; - return new Promise((resolve) => { - try { - const miroAppCardResponse = fetch( - `https://api.miro.com/v2/boards/${item.miroBoardId}/app_cards/${item.miroAppCardId}`, - options, - ); - - if (miroAppCardResponse.ok) { - const data = miroAppCardResponse.json(); - const response = { - statusCode: 200, - headers: { "content-type": "application/json" }, - body: JSON.stringify(data), - }; - resolve(response); - } else { - resolve({ - statusCode: miroAppCardResponse.status || 500, - body: miroAppCardResponse.statusText, - }); - } + try { + const miroAppCardResponse = await fetch( + `https://api.miro.com/v2/boards/${item.miroBoardId}/app_cards/${item.miroAppCardId}`, + options, + ); + if (miroAppCardResponse.ok) { + const data = await miroAppCardResponse.json(); const response = { statusCode: 200, headers: { "content-type": "application/json" }, body: JSON.stringify(data), }; - resolve(response); - } catch (error) { - console.log(error); - resolve({ - statusCode: error.statusCode || 500, - body: error.message, - }); + return response; + } else { + return { + statusCode: miroAppCardResponse.status || 500, + body: miroAppCardResponse.statusText, + }; } - }); + } catch (error) { + console.log(error); + return { + statusCode: error.statusCode || 500, + body: error.message, + }; + } }), ); }