├── slack
├── demo.png
├── install.png
├── readme.md
├── mod.ts
└── cities.ts
├── discord
├── demo.png
├── readme.md
└── mod.ts
├── telegram
├── demo.png
├── README.md
└── mod.ts
├── fetch
├── README.md
├── post.js
└── get.js
├── yaus
├── schema.gql
├── readme.md
└── mod.tsx
├── Makefile
├── issues
├── mod.js
├── pages
│ ├── 404.jsx
│ ├── home.jsx
│ └── api
│ │ └── issues.js
├── components
│ ├── layout.jsx
│ ├── search.jsx
│ └── card.jsx
├── readme.md
└── data
│ └── repositories.js
├── README.md
├── .github
└── workflows
│ └── ci.yml
├── LICENSE
├── post_request
├── readme.md
└── mod.js
└── json_html
├── README.md
└── mod.js
/slack/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denoland/deploy_examples/main/slack/demo.png
--------------------------------------------------------------------------------
/discord/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denoland/deploy_examples/main/discord/demo.png
--------------------------------------------------------------------------------
/slack/install.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denoland/deploy_examples/main/slack/install.png
--------------------------------------------------------------------------------
/telegram/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denoland/deploy_examples/main/telegram/demo.png
--------------------------------------------------------------------------------
/fetch/README.md:
--------------------------------------------------------------------------------
1 | # fetch() examples
2 |
3 | The examples here demonstrate how to make fetch() requests.
4 |
5 | - [`GET`](get.js) - makes a GET request to GitHub API endpoint.
6 | - [`POST`](post.js) - makes a POST request to https://post.deno.dev (echoes the
7 | request body back).
8 |
--------------------------------------------------------------------------------
/discord/readme.md:
--------------------------------------------------------------------------------
1 | # Discord Slash Command
2 |
3 | A simple Discord Slash Command.
4 |
5 |
6 |
7 | Read [the tutorial](https://deno.com/deploy/docs/tutorial-discord-slash) to
8 | learn how to create and deploy a Discord Slash Command.
9 |
--------------------------------------------------------------------------------
/yaus/schema.gql:
--------------------------------------------------------------------------------
1 | # An object containing the long url and corresponding short code.
2 | type Link {
3 | url: String! @unique
4 | code: String! @unique
5 | }
6 |
7 | # Queries to fetch code by url and vice versa.
8 | type Query {
9 | findCodeByUrl(url: String!): Link
10 | findUrlByCode(code: String!): Link
11 | }
12 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Check TypeScript types.
2 | check:
3 | deno cache fetch/get.js
4 | deno cache fetch/post.js
5 | deno cache post_request/mod.js
6 | deno cache json_html/mod.js
7 | deno cache issues/mod.js
8 | deno cache discord/mod.ts
9 | deno cache slack/mod.ts
10 | deno cache yaus/mod.tsx
11 | deno cache telegram/mod.ts
12 |
--------------------------------------------------------------------------------
/issues/mod.js:
--------------------------------------------------------------------------------
1 | import { serve } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 | import homePage from "./pages/home.jsx";
3 | import notFoundPage from "./pages/404.jsx";
4 | import issuesEndpoint from "./pages/api/issues.js";
5 |
6 | serve({
7 | "/": homePage,
8 | "/api/issues": issuesEndpoint,
9 | 404: notFoundPage,
10 | });
11 |
--------------------------------------------------------------------------------
/issues/pages/404.jsx:
--------------------------------------------------------------------------------
1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 | import Layout from "../components/layout.jsx";
3 |
4 | export default function notFoundPage(request) {
5 | return (
6 |
7 |
8 |
Page not found
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/issues/components/layout.jsx:
--------------------------------------------------------------------------------
1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 |
3 | export default function Layout({ children }) {
4 | return (
5 |
6 |
7 |
8 |
12 |
16 |
17 |
18 | {children}
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deno Deploy Examples
2 |
3 | This repository contains a list of examples for Deno Deploy.
4 |
5 | - [fetch](fetch) - Make outbound requests using the `fetch()` API.
6 | - [json_html](json_html) - Respond to requests with JSON or HTML.
7 | - [post_request](post_request) - Handle POST requests.
8 | - [slack](slack) - A Slack Slash Command example.
9 | - [discord](discord) - A Discord Slash Command example.
10 | - [yaus](yaus) - A URL shortener built on top of Deno Deploy and FaunaDB.
11 | - [issues](issues) - A server rendered (using JSX) website that displays issues
12 | with most comments of a GitHub repository.
13 | - [telegram](telegram) - A Telegram Bot Command example.
14 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 | on:
3 | push:
4 | branches: [main]
5 | pull_request:
6 | branches: [main]
7 |
8 | jobs:
9 | lint:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Clone repository
13 | uses: actions/checkout@v3
14 | with:
15 | submodules: false
16 | persist-credentials: false
17 |
18 | - name: Install Deno
19 | run: |-
20 | curl -fsSL https://deno.land/x/install/install.sh | sh
21 | echo "$HOME/.deno/bin" >> $GITHUB_PATH
22 |
23 | - name: Format
24 | run: deno fmt --check
25 |
26 | - name: Lint
27 | run: deno lint --unstable
28 |
29 | - name: Type Check
30 | run: make check
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Deno Land Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/issues/pages/home.jsx:
--------------------------------------------------------------------------------
1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 | import Card from "../components/card.jsx";
3 | import Layout from "../components/layout.jsx";
4 | import Search from "../components/search.jsx";
5 | import { getIssues } from "./api/issues.js";
6 |
7 | export default async function homePage(request) {
8 | const { searchParams } = new URL(request.url);
9 | const repo = searchParams.get("repository");
10 | const { repository, issues, code, message } = await getIssues(repo);
11 |
12 | return (
13 |
14 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/post_request/readme.md:
--------------------------------------------------------------------------------
1 | # Handle a POST Request
2 |
3 | This example demonstrates how to handle POST requests in Deno Deploy.
4 |
5 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/post_request/mod.js)
6 |
7 | - [Try Live Version](#try-live-version)
8 | - [Run offline](#run-offline)
9 |
10 | ## Try Live Version
11 |
12 | The example is deployed at https://post.deno.dev for demo.
13 |
14 | A POST request with JSON body:
15 |
16 | ```sh
17 | curl -X POST \
18 | -H 'content-type: application/json' \
19 | -d '{ "name": "Deno" }' https://post.deno.dev
20 | ```
21 |
22 | Response:
23 |
24 | ```json
25 | {
26 | "json": {
27 | "name": "Deno"
28 | }
29 | }
30 | ```
31 |
32 | A POST request with form data:
33 |
34 | ```sh
35 | curl -X POST -F 'name=Deno' https://post.deno.dev
36 | ```
37 |
38 | Response:
39 |
40 | ```json
41 | {
42 | "form": {
43 | "name": "Deno"
44 | }
45 | }
46 | ```
47 |
48 | ## Run Offline
49 |
50 | You can run the example program on your machine using
51 | [`deno`](https://github.com/denoland/deno):
52 |
53 | ```sh
54 | deno run https://raw.githubusercontent.com/denoland/deploy_examples/main/post_request/mod.js
55 | ```
56 |
--------------------------------------------------------------------------------
/issues/components/search.jsx:
--------------------------------------------------------------------------------
1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 |
3 | /** Validate if the form value follows the appropriate
4 | * structure (/). */
5 | function validateForm() {
6 | // deno-lint-ignore no-undef
7 | const repository = document.forms["search"]["repository"].value;
8 | if (repository.split("/").length !== 2) {
9 | alert(
10 | `Input should be in the form of 'owner/repository'. No forward slashes at the beginning or end.`,
11 | );
12 | return false;
13 | }
14 | return true;
15 | }
16 |
17 | export default function Search() {
18 | return (
19 |
20 |
21 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/fetch/post.js:
--------------------------------------------------------------------------------
1 | import { listenAndServe } from "https://deno.land/std@0.111.0/http/server.ts";
2 |
3 | async function handleRequest(_request) {
4 | // For making a POST request we need to specify the method property
5 | // as POST and provide data to the body property in the same object.
6 | // https://post.deno.dev echoes data we POST to it.
7 | const response = await fetch("https://post.deno.dev", {
8 | method: "POST",
9 | headers: {
10 | // This headers implies to the server that the content of
11 | // body is JSON and is encoded using UTF-8.
12 | "content-type": "application/json; charset=UTF-8",
13 | },
14 | body: JSON.stringify({
15 | message: "Hello from Deno Deploy.",
16 | }),
17 | });
18 |
19 | if (response.ok) {
20 | // The echo server returns the data back in
21 | const {
22 | json: { message },
23 | } = await response.json();
24 | return new Response(JSON.stringify({ message }), {
25 | headers: {
26 | "content-type": "application/json; charset=UTF-8",
27 | },
28 | });
29 | }
30 |
31 | return new Response(
32 | JSON.stringify({ message: "couldn't process your request" }),
33 | {
34 | status: 500,
35 | headers: {
36 | "content-type": "application/json; charset=UTF-8",
37 | },
38 | },
39 | );
40 | }
41 |
42 | console.log("Listening on http://localhost:8080");
43 | await listenAndServe(":8080", handleRequest);
44 |
--------------------------------------------------------------------------------
/issues/readme.md:
--------------------------------------------------------------------------------
1 | # Most Discussed Issues
2 |
3 | A small server rendered website that displays the most discussed issues of a
4 | repository.
5 |
6 | Visit [`https://issues.deno.dev`](https://issues.deno.dev) for a live version.
7 |
8 | - [Deploy](#deploy)
9 | - [Run Offline](#run-offline)
10 |
11 | ## Deploy
12 |
13 | Follow the steps under [`GitHub`](#github) section to obtain a GitHub token and
14 | click on the button below to deploy the application.
15 |
16 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/issues/mod.js&env=GITHUB_TOKEN)
17 |
18 | ### GitHub
19 |
20 | The application uses GitHub API to fetch issues sorted by most number of
21 | comments. And we need the GitHub PAT (Personal Access Token) to communicate with
22 | the API.
23 |
24 | Here are the steps to obtain one:
25 |
26 | 1. Go to https://github.com/settings/tokens
27 | 2. Click on **Generate new token**
28 | 3. Fill the **Note** field for your own reference
29 | 4. Scroll down (don't select any scopes) and click on **Generate token**
30 |
31 | That's it. You now have a token that you can use with the application.
32 |
33 | ## Run Offline
34 |
35 | You can run the application on your local machine using
36 | [`deno`](https://github.com/denoland/deno).
37 |
38 | ```
39 | GITHUB_TOKEN= deno run --allow-env --allow-net https://raw.githubusercontent.com/denoland/deploy_examples/main/issues/mod.js
40 | ```
41 |
42 | Replace `` with you GitHub token.
43 |
--------------------------------------------------------------------------------
/yaus/readme.md:
--------------------------------------------------------------------------------
1 | # Yet Another URL Shortener (YAUS)
2 |
3 | A URL shortener built on top of Deno Deploy and FaunaDB.
4 |
5 | Visit [`https://yaus.deno.dev`](https://yaus.deno.dev) for a live version.
6 |
7 | - [Deploy](#deploy)
8 | - [Run Offline](#run-offline)
9 |
10 | ## Deploy
11 |
12 | Follow the steps under [`Fauna`](#fauna) section to obtain a FaunaDB secret and
13 | click on the button below to deploy the application.
14 |
15 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/yaus/mod.tsx&env=FAUNA_SECRET)
16 |
17 | ### Fauna
18 |
19 | We use FaunaDB to store our application data. Follow the below steps to create a
20 | database and obtain a secret to access the DB from your Deno Deploy application.
21 |
22 | Create a new database:
23 |
24 | 1. Go to https://dashboard.fauna.com (login if required) and click on **New
25 | Database**
26 | 2. Fill the **Database Name** field and click on **Save**.
27 | 3. Click on **GraphQL** section visible on the left sidebar.
28 | 4. Download [`schema.gql`](schema.gql) to your local machine and import the
29 | file.
30 |
31 | Generate a secret to access the database:
32 |
33 | 1. Click on **Security** section and click on **New Key**.
34 | 2. Select **Server** role and click on **Save**. Copy the secret.
35 |
36 | ## Run Offline
37 |
38 | You can run the application on your local machine using
39 | [`deno`](https://github.com/denoland/deno).
40 |
41 | ```
42 | FAUNA_SECRET= deno run --allow-env --allow-net https://raw.githubusercontent.com/denoland/deploy_examples/main/yaus/mod.tsx
43 | ```
44 |
45 | Replace `` with your FaunaDB secret.
46 |
--------------------------------------------------------------------------------
/json_html/README.md:
--------------------------------------------------------------------------------
1 | # Respond with JSON and/or HTML
2 |
3 | This example demonstrates how to respond to requests with JSON and/or HTML in
4 | Deno Deploy.
5 |
6 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/json_html/mod.js)
7 |
8 | - [Try Live Version](#try-live-version)
9 | - [Run offline](#run-offline)
10 |
11 | ## Try Live Version
12 |
13 | The example is deployed at https://json-html.deno.dev for demo.
14 |
15 | Visit or curl `https://json-html.deno.dev/json` endpoint to get response in
16 | JSON.
17 |
18 | ```sh
19 | curl --dump-header - https://json-html.deno.dev/json
20 | # Response:
21 |
22 | # HTTP/2 200
23 | # content-type: application/json; charset=UTF-8
24 | # content-length: 36
25 | # date: Tue, 09 Mar 2021 15:11:57 GMT
26 | # server: denosr
27 | # x-dsr-id: asia-southeast1-a::runner-l4hc
28 | # {"message":"Hello from Deno Deploy"}
29 | ```
30 |
31 | Visit or curl `https://json-html.deno.dev/html` endpoint to get response in
32 | HTML.
33 |
34 | ```sh
35 | curl --dump-header - https://json-html.deno.dev/html
36 | # Response:
37 |
38 | # HTTP/2 200
39 | # content-type: text/html; charset=UTF-8
40 | # content-length: 73
41 | # date: Tue, 09 Mar 2021 15:15:56 GMT
42 | # server: denosr
43 | # x-dsr-id: asia-southeast1-a::runner-l4hc
44 | #
45 | #
Message: Hello from Deno Deploy.
46 | #
47 | ```
48 |
49 | ## Run Offline
50 |
51 | You can run the example program on your machine using
52 | [`deno`](https://github.com/denoland/deno):
53 |
54 | ```sh
55 | deno run https://raw.githubusercontent.com/denoland/deploy_examples/main/json_html/mod.js
56 | # Listening at http://localhost:8080
57 | ```
58 |
--------------------------------------------------------------------------------
/json_html/mod.js:
--------------------------------------------------------------------------------
1 | import { listenAndServe } from "https://deno.land/std@0.111.0/http/server.ts";
2 |
3 | function handleRequest(request) {
4 | const { pathname } = new URL(request.url);
5 |
6 | // Respond with HTML
7 | if (pathname.startsWith("/html")) {
8 | const html = `
9 |
Message: Hello from Deno Deploy.
10 | `;
11 |
12 | return new Response(html, {
13 | headers: {
14 | // The interpretation of the body of the response by the client depends
15 | // on the 'content-type' header.
16 | // The "text/html" part implies to the client that the content is HTML
17 | // and the "charset=UTF-8" part implies to the client that the content
18 | // is encoded using UTF-8.
19 | "content-type": "text/html; charset=UTF-8",
20 | },
21 | });
22 | }
23 |
24 | // Respond with JSON
25 | if (pathname.startsWith("/json")) {
26 | // Use stringify function to convert javascript object to JSON string.
27 | const json = JSON.stringify({
28 | message: "Hello from Deno Deploy",
29 | });
30 |
31 | return new Response(json, {
32 | headers: {
33 | "content-type": "application/json; charset=UTF-8",
34 | },
35 | });
36 | }
37 |
38 | return new Response(
39 | `
43 |
Return JSON and/or HTML Example
44 |
45 | /html - responds with HTML to the request.
46 |
47 |
48 | /json - responds with JSON to the request.
49 |
50 | `,
51 | {
52 | headers: {
53 | "content-type": "text/html; charset=UTF-8",
54 | },
55 | },
56 | );
57 | }
58 |
59 | console.log("Listening on http://localhost:8080");
60 | await listenAndServe(":8080", handleRequest);
61 |
--------------------------------------------------------------------------------
/fetch/get.js:
--------------------------------------------------------------------------------
1 | import { listenAndServe } from "https://deno.land/std@0.111.0/http/server.ts";
2 |
3 | async function handleRequest(_request) {
4 | // We pass the url as the first argument to fetch and an object with
5 | // additional info like headers, method, and body for POST requests as
6 | // the second argument. By default fetch makes a GET request,
7 | // so we can skip specifying method for GET requests.
8 | const response = await fetch("https://api.github.com/users/denoland", {
9 | headers: {
10 | // Servers use this header to decide on response body format.
11 | // "application/json" implies that we accept the data in JSON format.
12 | accept: "application/json",
13 | },
14 | });
15 |
16 | // The .ok property of response indicates that the request is
17 | // successful (status is in range of 200-299).
18 | if (response.ok) {
19 | // response.json() method reads the body and parses it as JSON.
20 | // It then returns the data in JavaScript object.
21 | const { name, login, avatar_url: avatar } = await response.json();
22 | return new Response(
23 | JSON.stringify({ name, username: login, avatar }),
24 | {
25 | headers: {
26 | "content-type": "application/json; charset=UTF-8",
27 | },
28 | },
29 | );
30 | }
31 | // fetch() doesn't throw for bad status codes. You need to handle them
32 | // by checking if the response.ok is true or false.
33 | // In this example we're just returning a generic error for simplicity but
34 | // you might want to handle different cases based on response status code.
35 | return new Response(
36 | JSON.stringify({ message: "couldn't process your request" }),
37 | {
38 | status: 500,
39 | headers: {
40 | "content-type": "application/json; charset=UTF-8",
41 | },
42 | },
43 | );
44 | }
45 |
46 | console.log("Listening on http://localhost:8080");
47 | await listenAndServe(":8080", handleRequest);
48 |
--------------------------------------------------------------------------------
/telegram/README.md:
--------------------------------------------------------------------------------
1 | # Telegram Bot Command
2 |
3 | A simple Telegram Bot Command.
4 |
5 | ## Tutorial
6 |
7 | 1. Follow the
8 | [official Telegram guide](https://core.telegram.org/bots#3-how-do-i-create-a-bot)
9 | for creating a Bot.
10 | 2. Deploy the Bot by clicking on this button:
11 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/telegram/mod.ts&env=TOKEN,BOT_NAME)
12 | 3. Input `TOKEN` and `BOT_NAME` env variable fields. The token value should be
13 | available from the BotFather and the value `BOT_NAME` is the bot username
14 | that ends with either `_bot` or `Bot`.
15 | 4. Click on **Create** to create the project, then on **Deploy** to deploy the
16 | script.
17 | 5. Grab the URL that's displayed under Domains in the Production Deployment
18 | card.
19 | 6. Visit the following URL (make sure to replace the template fields):
20 | ```
21 | https://api.telegram.org/bot/setWebhook?url=/
22 | ```
23 |
24 | > Replace with the token from the BotFather and ``
25 | > with the URL from the previous step.
26 |
27 | 7. Add a command to the bot by visiting the following URL:
28 |
29 | ```
30 | https://api.telegram.org/bot/setMyCommands?commands=[{"command":"ping","description":"Should return a 'pong' from the Bot."}]
31 | ```
32 | 8. Now you can invite the bot to a Group Chat or just PM the bot with the
33 | following command "/ping".
34 |
35 |
36 |
37 | ## Run Offline
38 |
39 | You can run the example program on your machine using
40 | [`deno`](https://github.com/denoland/deno):
41 |
42 | ```sh
43 | TOKEN= BOT_NAME= deno run --allow-env --allow-net https://raw.githubusercontent.com/denoland/deploy_examples/main/telegram/mod.ts
44 | ```
45 |
46 | You need to use a tool like [ngrok](https://ngrok.com) to tunnel Telegram
47 | requests to the app running on your machine.
48 |
49 | 1. Run `ngrok http 8080` (assuming that the application is running on port
50 | `8080`)
51 | 2. While registering the bot, use the https URL output by ngrok for **url**
52 | query.
53 |
54 | > Example:
55 | > `https://api.telegram.org/bot/setWebhook?url=/`
56 |
57 | That's it.
58 |
--------------------------------------------------------------------------------
/post_request/mod.js:
--------------------------------------------------------------------------------
1 | // Every request to a Deno Deploy program is considered as a fetch event.
2 | // So let's register our listener that will respond with the result of
3 | // our request handler on "fetch" events.
4 | addEventListener("fetch", (event) => {
5 | event.respondWith(handleRequest(event.request));
6 | });
7 |
8 | async function handleRequest(request) {
9 | if (request.method !== "POST") {
10 | return new Response(null, {
11 | status: 405,
12 | statusText: "Method Not Allowed",
13 | });
14 | }
15 |
16 | // We want the 'content-type' header to be present to be able to determine
17 | // the type of data sent by the client. So we respond to the client with
18 | // "Bad Request" status if the header is not available on the request.
19 | if (!request.headers.has("content-type")) {
20 | return new Response(
21 | JSON.stringify({ error: "please provide 'content-type' header" }),
22 | {
23 | status: 400,
24 | statusText: "Bad Request",
25 | headers: {
26 | "Content-Type": "application/json; charset=utf-8",
27 | },
28 | },
29 | );
30 | }
31 |
32 | const contentType = request.headers.get("content-type");
33 | const responseInit = {
34 | headers: {
35 | "Content-Type": "application/json; charset=utf-8",
36 | },
37 | };
38 |
39 | // Handle JSON data.
40 | if (contentType.includes("application/json")) {
41 | const json = await request.json();
42 | return new Response(JSON.stringify({ json }, null, 2), responseInit);
43 | }
44 |
45 | // Handle form data.
46 | if (
47 | contentType.includes("application/x-www-form-urlencoded") ||
48 | contentType.includes("multipart/form-data")
49 | ) {
50 | const formData = await request.formData();
51 | const formDataJSON = {};
52 | for (const [key, value] of formData.entries()) {
53 | formDataJSON[key] = value;
54 | }
55 | return new Response(
56 | JSON.stringify({ form: formDataJSON }, null, 2),
57 | responseInit,
58 | );
59 | }
60 |
61 | // Handle plain text.
62 | if (contentType.includes("text/plain")) {
63 | const text = await request.text();
64 | return new Response(JSON.stringify({ text }, null, 2), responseInit);
65 | }
66 |
67 | // Reaching here implies that we don't support the provided content-type
68 | // of the request so we reflect that back to the client.
69 | return new Response(null, {
70 | status: 415,
71 | statusText: "Unsupported Media Type",
72 | });
73 | }
74 |
--------------------------------------------------------------------------------
/issues/components/card.jsx:
--------------------------------------------------------------------------------
1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 | import { formatDistanceToNow } from "https://cdn.skypack.dev/pin/date-fns@v2.16.1-IRhVs8UIgU3f9yS5Yt4w/date-fns.js";
3 |
4 | export default function Card({
5 | url,
6 | title,
7 | state,
8 | comments,
9 | createdAt,
10 | closedAt,
11 | }) {
12 | return (
13 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/slack/readme.md:
--------------------------------------------------------------------------------
1 | # Slack Slash Command Example
2 |
3 | **Weather** - A Slack Slash Command to access weather information.
4 |
5 |
6 |
7 | - [Try Live Version](#try-live-version)
8 | - [Deploy](#deploy)
9 | - [Run Offline](#run-offline)
10 |
11 | ## Try Live Version
12 |
13 | We've a version deployed at https://weather.deno.dev for demo. You can use it to
14 | create the Slash Command in your Slack workspace.
15 |
16 | ### Installation
17 |
18 | Go to https://api.slack.com/apps?new_app and create an app. After successful app
19 | creation, click on "Slash Commands" section and fill out the details as shown
20 | below.
21 |
22 |
23 |
24 | After filling the details click on "Save" button that might at bottom right of
25 | the page. That's it!
26 |
27 | ### Usage
28 |
29 | After the Slack App is setup with the Slash command, run the below command to
30 | get weather information of a place.
31 |
32 | ```
33 | /weather []
34 | ```
35 |
36 | Additionally, you can avoid passing the city argument to get weather information
37 | of a random city from the [list](cities.js).
38 |
39 | ## Deploy
40 |
41 | Follow the steps under [`OpenWeather`](#openweather) section to obtain a
42 | OpenWeather API token and click on the button below to deploy the application.
43 |
44 | [](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/slack/mod.ts&env=OPEN_WEATHER_TOKEN)
45 |
46 | ### OpenWeather
47 |
48 | We use OpenWeather API to obtain weather information.
49 |
50 | Here are the steps to obtain a token to communicate with the API:
51 |
52 | 1. Go to https://home.openweathermap.org/api_keys (Login or Sign Up if required)
53 | 2. Name the key under **Create Key** and click on **Generate**
54 |
55 | That's it.
56 |
57 | ## Run Offline
58 |
59 | You can run the application on your local machine using
60 | [`deno`](https://github.com/denoland/deno).
61 |
62 | ```
63 | OPEN_WEATHER_TOKEN= deno run --allow-env --allow-net https://raw.githubusercontent.com/denoland/deploy_examples/main/slack/mod.ts
64 | ```
65 |
66 | Grab a token at https://openweathermap.org and set the value for the variable.
67 |
68 | To be able to use the local version on your Slack workspace, you need to use a
69 | tool like [ngrok](https://ngrok.com) to tunnel Slack requests to the app running
70 | on your machine.
71 |
72 | 1. Run `ngrok http 8000` (assuming that the application is running on port
73 | `8000`)
74 | 2. Follow the steps under Installation section, but use the https URL output by
75 | ngrok for **Request URL** field.
76 |
77 | That's it.
78 |
--------------------------------------------------------------------------------
/issues/pages/api/issues.js:
--------------------------------------------------------------------------------
1 | import { json, validateRequest } from "https://deno.land/x/sift@0.4.2/mod.ts";
2 | import repositories from "../../data/repositories.js";
3 |
4 | export default async function issuesEndpoint(request) {
5 | // We will only allow GET requests to this endpoint.
6 | const { error } = await validateRequest(request, {
7 | GET: {},
8 | });
9 | if (error) {
10 | return json({ error: error.message }, { status: error.status });
11 | }
12 |
13 | // The user can provide a param named 'repository'
14 | // to fetch issues for that repository.
15 | const { searchParams } = new URL(request.url);
16 | const repository = searchParams.get("repository");
17 |
18 | // Get the issues from GitHub.
19 | const { issues, code, message } = await getIssues(repository);
20 | if (code === "repoNotFound") {
21 | return json(
22 | {
23 | message,
24 | },
25 | { status: 404 },
26 | );
27 | }
28 |
29 | // Return the message with 500 status code if GitHub token is not set.
30 | if (code === "tokenNotAvailable") {
31 | return json(
32 | {
33 | message,
34 | },
35 | { status: 500 },
36 | );
37 | }
38 |
39 | return json({ issues }, { status: 200 });
40 | }
41 |
42 | /** Get issues with most comments for the provided repository. Default
43 | * to a random one from the top (most stars) 500 repositories. */
44 | export async function getIssues(repository) {
45 | if (!repository) {
46 | repository = repositories[Math.floor(Math.random() * 500)];
47 | }
48 |
49 | // Retrieve GitHub API token from env. Error if not set.
50 | const token = Deno.env.get("GITHUB_TOKEN");
51 | if (!token) {
52 | return {
53 | code: "tokenNotAvailable",
54 | message: "Environment variable GITHUB_TOKEN not set.",
55 | };
56 | }
57 |
58 | // Fetch issues for the provided repository.
59 | const response = await fetch(
60 | `https://api.github.com/repos/${repository}/issues?sort=comments&per_page=10&state=all`,
61 | {
62 | method: "GET",
63 | headers: {
64 | "User-Agent": "Deno Deploy",
65 | Accept: "application/vnd.github.v3+json",
66 | Authorization: `token ${token}`,
67 | },
68 | },
69 | );
70 |
71 | // Handle if the response isn't successful.
72 | if (!response.ok) {
73 | // If the repository is not found, reflect that with a message.
74 | if (response.status === 404) {
75 | return {
76 | code: "repoNotFound",
77 | message: `Repository '${repository}' not found`,
78 | };
79 | } else {
80 | return {
81 | code: "serverError",
82 | message: `Failed to retrieve issues from GitHub. Try again.`,
83 | };
84 | }
85 | }
86 |
87 | // Return the issues.
88 | const issues = await response.json();
89 | return {
90 | repository,
91 | issues: issues.map((issue) => ({
92 | url: issue.html_url,
93 | title: issue.title,
94 | state: issue.state,
95 | comments: issue.comments,
96 | createdAt: issue.created_at,
97 | closedAt: issue.closed_at,
98 | })),
99 | };
100 | }
101 |
--------------------------------------------------------------------------------
/discord/mod.ts:
--------------------------------------------------------------------------------
1 | // Sift is a small routing library that abstracts the details like registering
2 | // a fetch event listener and provides a simple function (serve) that has an
3 | // API to invoke a funciton for a specific path.
4 | import {
5 | json,
6 | serve,
7 | validateRequest,
8 | } from "https://deno.land/x/sift@0.4.2/mod.ts";
9 | // TweetNaCl is a cryptography library that we use to verify requests
10 | // from Discord.
11 | import nacl from "https://cdn.skypack.dev/tweetnacl@v1.0.3";
12 |
13 | // For all requests to "/" endpoint, we want to invoke home() handler.
14 | serve({
15 | "/": home,
16 | });
17 |
18 | // The main logic of the Discord Slash Command is defined in this function.
19 | async function home(request: Request) {
20 | // validateRequest() ensures that a request is of POST method and
21 | // has the following headers.
22 | const { error } = await validateRequest(request, {
23 | POST: {
24 | headers: ["X-Signature-Ed25519", "X-Signature-Timestamp"],
25 | },
26 | });
27 | if (error) {
28 | return json({ error: error.message }, { status: error.status });
29 | }
30 |
31 | // verifySignature() verifies if the request is coming from Discord.
32 | // When the request's signature is not valid, we return a 401 and this is
33 | // important as Discord sends invalid requests to test our verification.
34 | const { valid, body } = await verifySignature(request);
35 | if (!valid) {
36 | return json(
37 | { error: "Invalid request" },
38 | {
39 | status: 401,
40 | },
41 | );
42 | }
43 |
44 | const { type = 0, data = { options: [] } } = JSON.parse(body);
45 | // Discord performs Ping interactions to test our application.
46 | // Type 1 in a request implies a Ping interaction.
47 | if (type === 1) {
48 | return json({
49 | type: 1, // Type 1 in a response is a Pong interaction response type.
50 | });
51 | }
52 |
53 | // Type 2 in a request is an ApplicationCommand interaction.
54 | // It implies that a user has issued a command.
55 | if (type === 2) {
56 | const { value } = data.options.find(
57 | (option: { name: string }) => option.name === "name",
58 | );
59 | return json({
60 | // Type 4 reponds with the below message retaining the user's
61 | // input at the top.
62 | type: 4,
63 | data: {
64 | content: `Hello, ${value}!`,
65 | },
66 | });
67 | }
68 |
69 | // We will return a bad request error as a valid Discord request
70 | // shouldn't reach here.
71 | return json({ error: "bad request" }, { status: 400 });
72 | }
73 |
74 | /** Verify whether the request is coming from Discord. */
75 | async function verifySignature(
76 | request: Request,
77 | ): Promise<{ valid: boolean; body: string }> {
78 | const PUBLIC_KEY = Deno.env.get("DISCORD_PUBLIC_KEY")!;
79 | // Discord sends these headers with every request.
80 | const signature = request.headers.get("X-Signature-Ed25519")!;
81 | const timestamp = request.headers.get("X-Signature-Timestamp")!;
82 | const body = await request.text();
83 | const valid = nacl.sign.detached.verify(
84 | new TextEncoder().encode(timestamp + body),
85 | hexToUint8Array(signature),
86 | hexToUint8Array(PUBLIC_KEY),
87 | );
88 |
89 | return { valid, body };
90 | }
91 |
92 | /** Converts a hexadecimal string to Uint8Array. */
93 | function hexToUint8Array(hex: string) {
94 | return new Uint8Array(hex.match(/.{1,2}/g)!.map((val) => parseInt(val, 16)));
95 | }
96 |
--------------------------------------------------------------------------------
/telegram/mod.ts:
--------------------------------------------------------------------------------
1 | import {
2 | json,
3 | PathParams,
4 | serve,
5 | validateRequest,
6 | } from "https://deno.land/x/sift@0.4.2/mod.ts";
7 |
8 | // For all requests to "/" endpoint, we want to invoke handleTelegram() handler.
9 | // Recommend using a secret path in the URL, e.g. https://www.example.com/.
10 | serve({
11 | "/": () => new Response("Welcome to the Telegram Bot site."),
12 | "/:slug": handleTelegram,
13 | });
14 |
15 | // The main logic of the Telegram bot is defined in this function.
16 | async function handleTelegram(request: Request, params?: PathParams) {
17 | // Gets the environment variable TOKEN
18 | const TOKEN = Deno.env.get("TOKEN")!;
19 | // Gets the environment variable BOT_NAME
20 | const BOT_NAME = Deno.env.get("BOT_NAME")!;
21 |
22 | // If the environment variable TOKEN is not found, throw an error.
23 | if (!TOKEN) {
24 | throw new Error("environment variable TOKEN is not set");
25 | }
26 |
27 | // For using a secret path in the URL, e.g. https://www.example.com/. If wrong url return "invalid request".
28 | if (params?.slug != TOKEN) {
29 | return json(
30 | { error: "invalid request" },
31 | {
32 | status: 401,
33 | },
34 | );
35 | }
36 |
37 | // Make sure the request is a POST request.
38 | const { error } = await validateRequest(request, {
39 | POST: {},
40 | });
41 |
42 | // validateRequest populates the error if the request doesn't meet
43 | // the schema we defined.
44 | if (error) {
45 | return json({ error: error.message }, { status: error.status });
46 | }
47 |
48 | // Get the body of the request
49 | const body = await request.text();
50 | // Parse the raw JSON body from Telegrams webhook.
51 | const data = await JSON.parse(body);
52 |
53 | // Check if the method is a POST request and that there was somthing in the body.
54 | if (request.method === "POST") {
55 | // Cheack if the command was "/ping".
56 | if (
57 | data && data["message"] && data["message"]["text"] &&
58 | (data["message"]["text"].toLowerCase() == "/ping" ||
59 | data["message"]["text"].toLowerCase() ==
60 | "/ping@" + BOT_NAME.toLowerCase())
61 | ) {
62 | // Store the chat id of the Group Chat, Channel or PM.
63 | const chatId: number = data["message"]["chat"]["id"];
64 |
65 | // Calls the API service to Telegram for sending a message.
66 | const { dataTelegram, errors } = await sendMessage(
67 | chatId,
68 | "Pong",
69 | TOKEN,
70 | );
71 |
72 | if (errors) {
73 | console.error(errors.map((error) => error.message).join("\n"));
74 | return json({ error: "couldn't create the message" }, {
75 | status: 500,
76 | });
77 | }
78 |
79 | // Returns the answer and set status code 201.
80 | return json({ dataTelegram }, { status: 201 });
81 | }
82 | // Returns empty object and set status code 200.
83 | return json({}, { status: 200 });
84 | }
85 |
86 | // We will return a bad request error as a valid Telegram request
87 | // shouldn't reach here.
88 | return json({ error: "bad request" }, { status: 400 });
89 | }
90 |
91 | /** What to store for an error message. */
92 | type TelegramError = {
93 | message?: string;
94 | };
95 |
96 | /** Sending a POST request to Telegram's API to send a message. */
97 | async function sendMessage(
98 | chatId: number,
99 | text: string,
100 | token: string,
101 | ): Promise<{
102 | dataTelegram?: unknown;
103 | errors?: TelegramError[];
104 | }> {
105 | try {
106 | const res = await fetch(
107 | `https://api.telegram.org/bot${token}/sendMessage`,
108 | {
109 | method: "POST",
110 | headers: {
111 | "content-type": "application/json",
112 | },
113 | body: JSON.stringify({
114 | chat_id: chatId,
115 | text: text,
116 | }),
117 | },
118 | );
119 | const { dataTelegram, errors } = await res.json();
120 |
121 | if (errors) {
122 | return { dataTelegram, errors };
123 | }
124 |
125 | return { dataTelegram };
126 | } catch (error) {
127 | console.error(error);
128 | return { errors: [{ message: "failed to fetch data from Telegram" }] };
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/slack/mod.ts:
--------------------------------------------------------------------------------
1 | import {
2 | json,
3 | serve,
4 | validateRequest,
5 | } from "https://deno.land/x/sift@0.4.2/mod.ts";
6 | import { getCardinal } from "https://deno.land/x/cardinal@0.1.0/mod.ts";
7 | import { getRandomCity } from "./cities.ts";
8 |
9 | async function handleRequest(request: Request) {
10 | // validateRequest() ensures that incoming requests are of methods POST and GET.
11 | // Slack sends a POST request with a form field named text that contains the
12 | // information provided by user. We're allowing GET for anyone visiting the
13 | // endpoint in browser. You can disallow GET for your application as it is
14 | // not required by Slack.
15 | const { error } = await validateRequest(request, {
16 | GET: {},
17 | POST: {},
18 | });
19 | if (error) {
20 | // validateRequest() generates appropriate error and status code when
21 | // the request isn't valid. We return that information in a format that's
22 | // appropriate for Slack but there's a good chance that we will not
23 | // encounter this error if the request is actually coming from Slack.
24 | return json(
25 | // "ephemeral" indicates that the response is short-living and is only
26 | // shown to user who invoked the command in Slack.
27 | { response_type: "ephemeral", text: error.message },
28 | { status: error.status },
29 | );
30 | }
31 |
32 | // If a user is trying to visit the endpoint in a browser, let's return a html
33 | // page instructing the user to visit the GitHub page.
34 | if (request.method === "GET") {
35 | return new Response(
36 | `
40 |
41 | Visit GitHub
42 | page for instructions on how to install this Slash Command on your Slack workspace.
43 |