Skip to content

Commit faeeeb4

Browse files
committed
feat: persisted language selection
1 parent 7951b25 commit faeeeb4

File tree

2 files changed

+160
-161
lines changed

2 files changed

+160
-161
lines changed

docs/apps/quickstart.mdx

Lines changed: 140 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -53,31 +53,30 @@ If your app should be installed from outside Plane, generate the consent URL usi
5353
client ID from app creation. Provide this URL in the "Setup URL" field if you want the
5454
flow triggered from the marketplace.
5555

56-
<Tabs>
57-
<TabItem value="Python" default>
58-
59-
```python
60-
import os
61-
from urllib.parse import urlencode
62-
params = {
63-
"client_id": os.getenv("PLANE_CLIENT_ID"),
64-
"response_type": "code",
65-
"redirect_uri": os.getenv("PLANE_REDIRECT_URI"),
66-
}
67-
consent_url = f"https://api.plane.so/auth/o/authorize-app/?{urlencode(params)}"
68-
```
69-
</TabItem>
70-
<TabItem value="TypeScript">
71-
```typescript
72-
import { URLSearchParams } from 'url';
73-
const params = new URLSearchParams({
74-
client_id: process.env.PLANE_CLIENT_ID!,
75-
response_type: "code",
76-
redirect_uri: process.env.PLANE_REDIRECT_URI!,
77-
});
78-
const consentUrl = `https://api.plane.so/auth/o/authorize-app/?${params.toString()}`;
79-
```
80-
</TabItem>
56+
<Tabs queryString="lang" groupId="code-examples">
57+
<TabItem label="Python" value="python" default>
58+
```python
59+
import os
60+
from urllib.parse import urlencode
61+
params = {
62+
"client_id": os.getenv("PLANE_CLIENT_ID"),
63+
"response_type": "code",
64+
"redirect_uri": os.getenv("PLANE_REDIRECT_URI"),
65+
}
66+
consent_url = f"https://api.plane.so/auth/o/authorize-app/?{urlencode(params)}"
67+
```
68+
</TabItem>
69+
<TabItem label="TypeScript" value="typescript">
70+
```typescript
71+
import { URLSearchParams } from 'url';
72+
const params = new URLSearchParams({
73+
client_id: process.env.PLANE_CLIENT_ID!,
74+
response_type: "code",
75+
redirect_uri: process.env.PLANE_REDIRECT_URI!,
76+
});
77+
const consentUrl = `https://api.plane.so/auth/o/authorize-app/?${params.toString()}`;
78+
```
79+
</TabItem>
8180
</Tabs>
8281

8382
There are two types of authenticated actions your app can perform:
@@ -99,29 +98,29 @@ callback. Use this to request a bot token for your app.
9998

10099
#### Example
101100

102-
<Tabs>
103-
<TabItem value="Python" default>
104-
```python
105-
import base64
106-
import requests
107-
client_id = "your_client_id"
108-
client_secret = "your_client_secret"
109-
basic_auth = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()
110-
payload = {
111-
"grant_type": "client_credentials",
112-
"app_installation_id": app_installation_id
113-
}
114-
response = requests.post(
115-
url="https://api.plane.so/auth/o/token/",
116-
headers={"Authorization": f"Basic {basic_auth}", "Content-Type": "application/x-www-form-urlencoded"},
117-
data=payload
118-
)
119-
response_data = response.json()
120-
bot_token = response_data['access_token']
121-
expires_in = response_data["expires_in"]
122-
```
123-
</TabItem>
124-
<TabItem value="TypeScript">
101+
<Tabs queryString="lang" groupId="code-examples">
102+
<TabItem label="Python" value="python" default>
103+
```python
104+
import base64
105+
import requests
106+
client_id = "your_client_id"
107+
client_secret = "your_client_secret"
108+
basic_auth = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()
109+
payload = {
110+
"grant_type": "client_credentials",
111+
"app_installation_id": app_installation_id
112+
}
113+
response = requests.post(
114+
url="https://api.plane.so/auth/o/token/",
115+
headers={"Authorization": f"Basic {basic_auth}", "Content-Type": "application/x-www-form-urlencoded"},
116+
data=payload
117+
)
118+
response_data = response.json()
119+
bot_token = response_data['access_token']
120+
expires_in = response_data["expires_in"]
121+
```
122+
</TabItem>
123+
<TabItem label="TypeScript" value="typescript">
125124
```typescript
126125
import axios from 'axios';
127126
const clientId = "your_client_id";
@@ -162,38 +161,38 @@ Plane will make a GET request to the Redirect URI with:
162161
| state | The state parameter passed in the authorization request | No |
163162

164163
#### Example
165-
<Tabs>
166-
<TabItem value="Python" default>
167-
```python
168-
import requests
169-
code = "authorization_code_from_callback"
170-
client_id = "your_client_id"
171-
client_secret = "your_client_secret"
172-
redirect_uri = "your_redirect_uri"
173-
payload = {
174-
"grant_type": "authorization_code",
175-
"code": code,
176-
"client_id": client_id,
177-
"client_secret": client_secret,
178-
"redirect_uri": redirect_uri
179-
}
180-
response = requests.post(
181-
url="https://api.plane.so/auth/o/token/",
182-
headers={"Content-Type": "application/x-www-form-urlencoded"},
183-
data=payload
184-
)
185-
response_data = response.json()
186-
access_token = response_data["access_token"]
187-
refresh_token = response_data["refresh_token"]
188-
expires_in = response_data["expires_in"]
189-
```
190-
</TabItem>
191-
<TabItem value="TypeScript">
192-
```typescript
193-
import axios from 'axios';
194-
const code = "authorization_code_from_callback";
195-
const clientId = "your_client_id";
196-
const clientSecret = "your_client_secret";
164+
<Tabs queryString="lang" groupId="code-examples">
165+
<TabItem label="Python" value="python" default>
166+
```python
167+
import requests
168+
code = "authorization_code_from_callback"
169+
client_id = "your_client_id"
170+
client_secret = "your_client_secret"
171+
redirect_uri = "your_redirect_uri"
172+
payload = {
173+
"grant_type": "authorization_code",
174+
"code": code,
175+
"client_id": client_id,
176+
"client_secret": client_secret,
177+
"redirect_uri": redirect_uri
178+
}
179+
response = requests.post(
180+
url="https://api.plane.so/auth/o/token/",
181+
headers={"Content-Type": "application/x-www-form-urlencoded"},
182+
data=payload
183+
)
184+
response_data = response.json()
185+
access_token = response_data["access_token"]
186+
refresh_token = response_data["refresh_token"]
187+
expires_in = response_data["expires_in"]
188+
```
189+
</TabItem>
190+
<TabItem label="TypeScript" value="typescript">
191+
```typescript
192+
import axios from 'axios';
193+
const code = "authorization_code_from_callback";
194+
const clientId = "your_client_id";
195+
const clientSecret = "your_client_secret";
197196
const redirectUri = "your_redirect_uri";
198197
const payload = {
199198
grant_type: "authorization_code",
@@ -225,29 +224,29 @@ In both flows, the `app_installation_id` identifies the app's installation withi
225224
workspace. Fetch workspace details after OAuth is completed. Use the
226225
`app-installation` endpoint with either type of token.
227226

228-
<Tabs>
229-
<TabItem value="Python" default>
230-
```python
231-
import requests
232-
headers = {"Authorization": f"Bearer {token}"}
233-
response = requests.get(
234-
url=f"https://api.plane.so/auth/o/app-installation/?id={app_installation_id}",
235-
headers=headers
236-
)
237-
workspace_details = response.data[0]
238-
```
239-
</TabItem>
240-
<TabItem value="TypeScript">
241-
```typescript
242-
import axios from 'axios';
243-
const headers = { Authorization: `Bearer ${token}` };
244-
const response = await axios.get(
245-
`https://api.plane.so/auth/o/app-installation/?id=${app_installation_id}`,
246-
{ headers }
247-
);
248-
const workspaceDetails = response.data[0];
249-
```
250-
</TabItem>
227+
<Tabs queryString="lang" groupId="code-examples">
228+
<TabItem label="Python" value="python" default>
229+
```python
230+
import requests
231+
headers = {"Authorization": f"Bearer {token}"}
232+
response = requests.get(
233+
url=f"https://api.plane.so/auth/o/app-installation/?id={app_installation_id}",
234+
headers=headers
235+
)
236+
workspace_details = response.data[0]
237+
```
238+
</TabItem>
239+
<TabItem label="TypeScript" value="typescript">
240+
```typescript
241+
import axios from 'axios';
242+
const headers = { Authorization: `Bearer ${token}` };
243+
const response = await axios.get(
244+
`https://api.plane.so/auth/o/app-installation/?id=${app_installation_id}`,
245+
{ headers }
246+
);
247+
const workspaceDetails = response.data[0];
248+
```
249+
</TabItem>
251250
</Tabs>
252251

253252
#### Sample Response
@@ -288,45 +287,45 @@ Use the access token to make authenticated requests to Plane via the
288287

289288
When the access token expires, use the refresh token to obtain a new access token.
290289

291-
<Tabs>
292-
<TabItem value="Python" default>
293-
```python
294-
refresh_payload = {
295-
"grant_type": "refresh_token",
296-
"refresh_token": refresh_token,
297-
"client_id": client_id,
298-
"client_secret": client_secret
299-
}
300-
refresh_response = requests.post(
301-
url="https://api.plane.so/auth/o/token/",
302-
headers={"Content-Type": "application/x-www-form-urlencoded"},
303-
data=refresh_payload
304-
)
305-
refresh_response_data = refresh_response.json()
306-
access_token = refresh_response_data["access_token"]
307-
```
308-
</TabItem>
309-
<TabItem value="TypeScript">
310-
```typescript
311-
const refreshPayload = {
312-
grant_type: "refresh_token",
313-
refresh_token: refreshToken,
314-
client_id: clientId,
315-
client_secret: clientSecret
316-
};
317-
const refreshResponse = await axios.post(
318-
"https://api.plane.so/auth/o/token/",
319-
refreshPayload,
320-
{
321-
headers: {
322-
"Content-Type": "application/x-www-form-urlencoded"
323-
}
290+
<Tabs queryString="lang" groupId="code-examples">
291+
<TabItem label="Python" value="python" default>
292+
```python
293+
refresh_payload = {
294+
"grant_type": "refresh_token",
295+
"refresh_token": refresh_token,
296+
"client_id": client_id,
297+
"client_secret": client_secret
324298
}
325-
);
326-
const refreshResponseData = refreshResponse.data;
327-
const accessToken = refreshResponseData.access_token;
328-
```
329-
</TabItem>
299+
refresh_response = requests.post(
300+
url="https://api.plane.so/auth/o/token/",
301+
headers={"Content-Type": "application/x-www-form-urlencoded"},
302+
data=refresh_payload
303+
)
304+
refresh_response_data = refresh_response.json()
305+
access_token = refresh_response_data["access_token"]
306+
```
307+
</TabItem>
308+
<TabItem label="TypeScript" value="typescript">
309+
```typescript
310+
const refreshPayload = {
311+
grant_type: "refresh_token",
312+
refresh_token: refreshToken,
313+
client_id: clientId,
314+
client_secret: clientSecret
315+
};
316+
const refreshResponse = await axios.post(
317+
"https://api.plane.so/auth/o/token/",
318+
refreshPayload,
319+
{
320+
headers: {
321+
"Content-Type": "application/x-www-form-urlencoded"
322+
}
323+
}
324+
);
325+
const refreshResponseData = refreshResponse.data;
326+
const accessToken = refreshResponseData.access_token;
327+
```
328+
</TabItem>
330329
</Tabs>
331330

332331
## Listing Your App on Plane Marketplace

docs/webhooks/overview.mdx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,26 @@ payload and your secret, the payload is authentic.
149149
import Tabs from '@theme/Tabs';
150150
import TabItem from '@theme/TabItem';
151151

152-
<Tabs>
153-
<TabItem value="NodeJS" label="NodeJS" default>
154-
```javascript
155-
// Fastify example
152+
<Tabs queryString="lang" groupId="code-examples">
153+
<TabItem label="Python" value="python">
154+
```python
155+
import hashlib
156+
import hmac
157+
158+
secret_token = os.environ.get("WEBHOOK_SECRET")
159+
160+
received_signature = request.headers.get('X-Plane-Signature')
161+
received_payload = json.dumps(request.json).encode('utf-8')
162+
163+
expected_signature = hmac.new(secret_token.encode('utf-8'), msg=received_payload, digestmod=hashlib.sha256).hexdigest()
164+
165+
if not hmac.compare_digest(expected_signature, received_signature):
166+
raise HTTPException(status_code=403, detail="Invalid Signature provided")
167+
```
168+
</TabItem>
169+
<TabItem value="typescript" label="TypeScript">
170+
```typescript
171+
// Node.js Fastify example
156172
import { createHmac } from 'node:crypto';
157173
const secret = process.env.WEBHOOK_SECRET;
158174

@@ -175,22 +191,6 @@ import TabItem from '@theme/TabItem';
175191
})
176192
```
177193
</TabItem>
178-
<TabItem value="python" label="Python">
179-
```python
180-
import hashlib
181-
import hmac
182-
183-
secret_token = os.environ.get("WEBHOOK_SECRET")
184-
185-
received_signature = request.headers.get('X-Plane-Signature')
186-
received_payload = json.dumps(request.json).encode('utf-8')
187-
188-
expected_signature = hmac.new(secret_token.encode('utf-8'), msg=received_payload, digestmod=hashlib.sha256).hexdigest()
189-
190-
if not hmac.compare_digest(expected_signature, received_signature):
191-
raise HTTPException(status_code=403, detail="Invalid Signature provided")
192-
```
193-
</TabItem>
194194
</Tabs>
195195

196196
:::tip Questions?

0 commit comments

Comments
 (0)