5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

株式会社船井総研デジタルAdvent Calendar 2022

Day 1

Azureに関するStackover flowの質問を誰よりも早く回答するために、Node.js、Azure Functions、CosmosDB、StackExchange API、Slack APIでサーバレスアプリケーションを作ってみる。

Last updated at Posted at 2022-11-30

arch5.png

はじめに

azure functionを使って、サーバレスらしい小さなアプリケーションを作って見たかったので作ってみました。コンセプト的には、「azureに関するstack overflowの質問を一番に回答してやろう」的なやつです。そのための機能として、以下を実装していきます。

  • 毎日stackoverflowのazureに関する質問を取得する
  • 取得した質問をデータベースに保存する
  • 質問をslackに送信する
  • REST APIからデータベースに保存した質問を取得できる

ようするに、毎日最新の stack overflow の azure に関する質問を slack のチャンネルに送信してくれるアプリケーションです。実際に回答をするかはあなた次第です。あと一応データベースに保存してあるので、REST API も作っておきます。Web インターフェイスも欲しくなるかもしれないので。

アプリケーション自体は、すべてazure functionsとサードパーティーのAPIを使って開発します。サーバレス感が少しでもあるかな。

Slack API の準備

Slack アカウントの作成

さっそく、Slack のアカウントを作成していきます。完全無料なので気にせず作っちゃってください。

Screen Shot 2022-11-01 at 13.54.05.png
Screen Shot 2022-11-01 at 13.55.51.png
Screen Shot 2022-11-01 at 13.57.09.png
Screen Shot 2022-11-01 at 13.58.31.png
Screen Shot 2022-11-01 at 13.59.07.png
Screen Shot 2022-11-01 at 14.00.23.png

Web Hook アプリを追加

slack に Node.js からメッセージを飛ばすために、web hook を追加します。slackアプリとして簡単に追加できます。

Screen Shot 2022-11-01 at 14.01.23.png
Screen Shot 2022-11-01 at 14.05.11.png
Screen Shot 2022-11-01 at 14.06.03.png

Incoming Webhook アプリの追加先のチャンネルはどれでもいいです。好きなのを選んでください。

Screen Shot 2022-11-01 at 14.06.57.png
Screen Shot 2022-11-01 at 14.07.47.png

アプリの追加ができたら、Webhook URL が発行されます。その URL を使うと簡単に slack にメッセージを飛ばす事ができます。

Screen Shot 2022-11-01 at 14.12.20.png

POST Method で body に JSON で {"text": "<飛ばしたいメッセージ>"}と送信すれば、textの内容がチャンネルに送信されます。実際にcurlコマンドを使って送信してみます。

curl -i -X POST -d '{"text": "hey from world"}' https://hooks.slack.com/services/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
❯ curl -i -X POST -d '{"text": "hey from world"}' https://hooks.slack.com/services/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
HTTP/2 200 
date: Wed, 02 Nov 2022 01:44:20 GMT
server: Apache
x-powered-by: HHVM/4.153.1
x-frame-options: SAMEORIGIN
access-control-allow-origin: *
referrer-policy: no-referrer
x-slack-backend: r
x-slack-unique-id: Y2HLdAI4tX4cMk8AL3ofXgAAAAU
strict-transport-security: max-age=31536000; includeSubDomains; preload
vary: Accept-Encoding
content-type: text/html
x-envoy-upstream-service-time: 190
x-backend: main_normal main_bedrock_normal_with_overflow main_canary_with_overflow main_bedrock_canary_with_overflow main_control_with_overflow main_bedrock_control_with_overflow
x-server: slack-www-hhvm-main-iad-cplt
x-slack-shared-secret-outcome: no-match
via: envoy-www-iad-ojmb, envoy-edge-nrt-75g0
x-edge-backend: envoy-www
x-slack-edge-shared-secret-outcome: no-match

ok

しっかりメッセージを受け取ることができました。
Screen Shot 2022-11-02 at 10.50.01.png

StackExchange API の準備

StackExchange API を使うと簡単に stack overflow の質問を取得することができます。もちろん無料です。

search GET

今回は、azure に関する質問を検索したいので、search GET の エンドポイントを使います。

フォームに任意の値を入力するだけで、APIリクエストに必要なURLをビルドしてくれる。便利。

リクエスト内容は:

  • azureに関する質問
  • azureのタグ付けされた質問
  • 1ページ目
  • 1ページあたりの質問の数は10個
  • 投稿日を基準にソート
  • 順番はデサント
  • タイトルにazureキーワードが入っている

Run ボタンをクリックすると実査にリクエストを送ることができる。

生成された URL をどこかへメモしておこう。後で使います。

Screen Shot 2022-11-02 at 10.57.22.png

Azure リソース準備

必要なazureリソースは:

  • Azure Storage Account
  • Azure function App
  • CosmosDB

の3つだけ、それぞれの作り方は前回の記事 Node.js(TypeScript)とAzure FunctionsでサーバレスなREST APIを作り、おまけにCosmosDBのChange Feedを受信してみる。で、で説明しているので、そちらを参照してね。

今回はとりあえず、次のシェルスクリプトを実行してね。自動的に必要なリソースが作られるはずなので。

let randomIdentifier=$RANDOM*$RANDOM

readonly AZ_RESOURCE_GROUP="myTestingRG${randomIdentifier}"
readonly AZ_RESOURCE_GROUP_LOCATION="japaneast"
readonly AZ_STORAGE_ACCOUNT="mytestsa${randomIdentifier}"
readonly AZ_STORAGE_ACCOUNT_SKU="Standard_LRS"
readonly AZ_FUNCTION="myTestFuncApp${randomIdentifier}"
readonly AZ_FUNCTION_VERSION="4"
readonly AZ_FUNCTION_RUNTIME="node"
readonly AZ_COSMOS_DB="cosmosdb${randomIdentifier}"

echo "Checking the specifed resource group..."
RG_FOUND=$(az group show -g ${AZ_RESOURCE_GROUP} -o tsv --query "properties.provisioningState")
if [ "${RG_FOUND}" = "Succeeded" ]; then
    echo "The resource group: ${AZ_RESOURCE_GROUP} already exists."
    exit
else
    echo "Creating a new resource group..."
    az group create \
        --name ${AZ_RESOURCE_GROUP} \
        --location ${AZ_RESOURCE_GROUP_LOCATION}
    echo "Done! ${AZ_RESOURCE_GROUP} has been created."
fi

echo "Creating storage account..."
az storage account create \
    --name ${AZ_STORAGE_ACCOUNT} \
    --location ${AZ_RESOURCE_GROUP_LOCATION} \
    --resource-group ${AZ_RESOURCE_GROUP} \
    --sku ${AZ_STORAGE_ACCOUNT_SKU}
echo "Done! ${AZ_STORAGE_ACCOUNT} has been created."

echo "Creating azure function app..."
az functionapp create \
    --name ${AZ_FUNCTION} \
    --storage-account ${AZ_STORAGE_ACCOUNT} \
    --consumption-plan-location ${AZ_RESOURCE_GROUP_LOCATION} \
    --resource-group ${AZ_RESOURCE_GROUP} \
    --functions-version ${AZ_FUNCTION_VERSION} \
    --runtime ${AZ_FUNCTION_RUNTIME}
echo "Done! ${AZ_FUNCTION} has been created."

echo "Creating cosmosdb..."
az cosmosdb create \
    --resource-group ${AZ_RESOURCE_GROUP} \
    --name ${AZ_COSMOS_DB} \
    --enable-free-tier true \
    --enable-analytical-storage false
echo "Done! ${AZ_COSMOS_DB} has been created."

db_endpoint=$(az cosmosdb show \
    --resource-group ${AZ_RESOURCE_GROUP} \
    --name ${AZ_COSMOS_DB} \
    -o tsv \
    --query "documentEndpoint")

primary_key=$(az cosmosdb keys list \
    --resource-group ${AZ_RESOURCE_GROUP} \
    --name ${AZ_COSMOS_DB} \
    --type "keys" \
    -o tsv \
    --query "primaryMasterKey")

cat <<EOF
# CosmosDB endpoint
${db_endpoint}
# CosmosDB primary key
${primary_key}
# Delete the resource group
az group delete --resource-group ${AZ_RESOURCE_GROUP} -y
EOF

init_azure.shというファイルを作って、上記のスクリプトをコピペして、chmod +x <path to file>で実行権を付与して、実行してね。そこそこ時間がかかるので、今のうちにトイレ、洗濯や晩ごはんの用意でもしておいてください。

chmod +x ./init_azure.sh && ./init_azure.sh
chmod +x ./init_azure.sh && ./init_azure.sh
Checking the specifed resource group...
ERROR: (ResourceGroupNotFound) Resource group 'myTestingRG97974569' could not be found.
Code: ResourceGroupNotFound
Message: Resource group 'myTestingRG97974569' could not be found.
Creating a new resource group...
{
  "id": "/subscriptions/783114d3-ab73-471f-8f7e-6f1a5776e86d/resourceGroups/myTestingRG97974569",
  "location": "japaneast",
  "managedBy": null,
  "name": "myTestingRG97974569",
  "properties": {

    # 省略...

  "writeLocations": [
    {
      "documentEndpoint": "https://cosmosdb97974569-japaneast.documents.azure.com:443/",
      "failoverPriority": 0,
      "id": "cosmosdb97974569-japaneast",
      "isZoneRedundant": false,
      "locationName": "Japan East",
      "provisioningState": "Succeeded"
    }
  ]
}
Done! cosmosdb97974569 has been created.
# CosmosDB endpoint
https://cosmosdb97974569.documents.azure.com:443/
# CosmosDB primary key
53FiqxcKH6xk3O4fwT8ORoTYCPMPGvD8u0UGFAMNxblDfbG5JoMpyku4CKuZ01vmaB1HrIjMHsH2ACDbuhucwQ==
# Delete the resource group
az group delete --resource-group myTestingRG97974569 -y

すべてのリソースが作り終わると、CosmosDBに接続するために必要なCosmosDB EndpointとCosmosDB primary keyがログとして表示される。後で必要になるので、どこかへメモしておこう。

     # 省略...

# CosmosDB endpoint
https://cosmosdb97974569.documents.azure.com:443/
# CosmosDB primary key
53FiqxcKH6xk3O4fwT8ORoTYCPMPGvD8u0UGFAMNxblDfbG5JoMpyku4CKuZ01vmaB1HrIjMHsH2ACDbuhucwQ==

     # 省略...

ちなみに以下のコマンドで今つくったすべてのリソースをリソースグループまとめて消去できます。

     # 省略...

# Delete the resource group
az group delete --resource-group myTestingRG97974569 -y

Azure funtion プロジェクトの準備

プロジェクトフォルダーの作成

project="stackover-flow-question-notifier"
mkdir ${project}
cd ${project}
npm init -y

ライブラリーのインストール

npm install -E @azure/cosmos axios

開発ライブラリーのインストール

npm install -E -D @azure/functions @types/node azure-functions-core-tools typescript azurite

Azure functionプロジェクトの生成

npx func init --worker-runtime node --language typescript

package.json の編集

{
  "name": "stackover-flow-question-notifier",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "build": "tsc", # <= 追加
    "watch": "tsc -w", # <= 追加
    "prestart": "npm run build", # <= 追加
    "start": "func start" # <= 追加
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@azure/cosmos": "3.17.1",
    "axios": "1.1.3"
  },
  "devDependencies": {
    "@azure/functions": "3.2.0",
    "@types/node": "18.11.8",
    "azure-functions-core-tools": "4.0.4829",
    "azurite": "3.20.1",
    "typescript": "4.8.4"
  }
}

Question fetcher (Timer trigger function)の作成

npx func new --name question-fetcher --template 'Timer trigger' --language typescript

timer function は azure storage がないと動かないので、azuriteでローカルでエミュレーターを動かす。

npx azurite

ローカルの azure storage を使うよう設定する。

local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true" # <= 編集!
  }
}

とりあえず動かす。

npm run start
❯ npm run start

> stackover-questions@1.0.0 prestart
> npm run build


> stackover-questions@1.0.0 build
> tsc


> stackover-questions@1.0.0 start
> func start


Azure Functions Core Tools
Core Tools Version:       4.0.4829 Commit hash: N/A  (64-bit)
Function Runtime Version: 4.11.2.19273


Functions:

        question-fetcher: timerTrigger

For detailed output, run func with --verbose flag.
[2022-11-02T23:45:31.577Z] Worker process started and initialized.
[2022-11-02T23:45:36.402Z] Host lock lease acquired by instance ID '000000000000000000000000D2A5BD45'.
[2022-11-02T23:50:00.062Z] Executing 'Functions.question-fetcher2' (Reason='Timer fired at 2022-11-02T08:50:00.0257450+09:00', Id=c7c99dd8-7120-4e0c-9018-a8bdfc1142b1)
[2022-11-02T23:50:00.102Z] Timer trigger function ran! 2022-11-02T23:50:00.097Z

timer functionの実行の頻度は function.jsonbindings[0].schedule で設定できる。デフォルトは5分に一回実行されるように設定されている。

function.json
{
  "bindings": [
    {
      "name": "myTimer",
      "type": "timerTrigger",
      "direction": "in",
      "schedule": "0 */5 * * * *"
    }
  ],
  "scriptFile": "../dist/question-fetcher/index.js"
}

Cron表記は以下のサイトを使うと簡単に作る事ができる。

ちなみに、毎晩夜の00:00に設定するなら以下のように書いてあげればいいです。

0 0 * * *

Timer function から StackExchange API に通信する

StackExchange API の web documentatiion で生成した URL を使って API から Stack Overflow の質問を取得する。

question-fetcher/index.ts
import axios from 'axios'
import { AzureFunction, Context } from "@azure/functions"

const timerTrigger: AzureFunction = async function (context: Context, myTimer: any): Promise<void> {
  const yesterday = new Date();
  yesterday.setDate(yesterday.getDate() - 1);
  const yesterdayEpoch = Math.floor(yesterday.getTime() / 1000);
  const todayEpoch = Math.floor(new Date().getTime() / 1000);
  const searchQuery = "azure";
  const endpoint = `https://api.stackexchange.com/2.3/search?page=1&pagesize=10&fromdate=${yesterdayEpoch}&todate=${todayEpoch}&order=desc&sort=creation&tagged=${searchQuery}&intitle=${searchQuery}&site=stackoverflow`;
  const response = await axios.get(endpoint);
  console.log(response.data) 
};

export default timerTrigger;

fromdatetodate の フォーマットは unix epoch time なので 以下のコードでそれぞれ計算する。

quetion-fetcher.ts
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const yesterdayEpoch = Math.floor(yesterday.getTime() / 1000);
const todayEpoch = Math.floor(new Date().getTime() / 1000);

Dateタイプ

Screen Shot 2022-11-02 at 11.12.18.png

実際に、 function を実行してみるとコンソールに取得された質問が出力される。

❯ npm run start

> stackover-questions@1.0.0 prestart
> npm run build


> stackover-questions@1.0.0 build
> tsc


> stackover-questions@1.0.0 start
> func start


Azure Functions Core Tools
Core Tools Version:       4.0.4829 Commit hash: N/A  (64-bit)
Function Runtime Version: 4.11.2.19273


Functions:

        question-fetcher: timerTrigger

For detailed output, run func with --verbose flag.
[2022-11-07T00:36:54.473Z] Worker process started and initialized.
[2022-11-07T00:36:59.277Z] Host lock lease acquired by instance ID '000000000000000000000000D2A5BD45'.
[2022-11-07T00:40:00.053Z] Executing 'Functions.question-fetcher2' (Reason='Timer fired at 2022-11-07T09:40:00.0311650+09:00', Id=f330c993-9431-4528-b86d-24d8a5410845)
[2022-11-07T00:40:01.061Z] {
[2022-11-07T00:40:01.061Z]   items: [
[2022-11-07T00:40:01.061Z]     {
[2022-11-07T00:40:01.062Z]       tags: [Array],
[2022-11-07T00:40:01.062Z]       owner: [Object],
[2022-11-07T00:40:01.062Z]       is_answered: false,
[2022-11-07T00:40:01.062Z]       view_count: 6,
[2022-11-07T00:40:01.062Z]       answer_count: 0,
[2022-11-07T00:40:01.062Z]       score: 0,
[2022-11-07T00:40:01.062Z]       last_activity_date: 1667779944,
[2022-11-07T00:40:01.062Z]       creation_date: 1667779944,
[2022-11-07T00:40:01.062Z]       question_id: 74340759,
[2022-11-07T00:40:01.062Z]       content_license: 'CC BY-SA 4.0',
[2022-11-07T00:40:01.062Z]       link: 'https://stackoverflow.com/questions/74340759/how-can-i-get-all-instance-guids-runnig-on-my-app-service-in-azure-using-powers',
[2022-11-07T00:40:01.062Z]       title: 'How can i get all instance guid&#39;s runnig on my app service in Azure using powershelll?'
[2022-11-07T00:40:01.062Z]     },
            # 省略
[2022-11-07T00:40:01.067Z]     {
[2022-11-07T00:40:01.067Z]       tags: [Array],
[2022-11-07T00:40:01.067Z]       owner: [Object],
[2022-11-07T00:40:01.067Z]       is_answered: false,
[2022-11-07T00:40:01.067Z]       view_count: 27,
[2022-11-07T00:40:01.067Z]       answer_count: 0,
[2022-11-07T00:40:01.068Z]       score: -1,
[2022-11-07T00:40:01.068Z]       last_activity_date: 1667719596,
[2022-11-07T00:40:01.068Z]       creation_date: 1667715323,
[2022-11-07T00:40:01.068Z]       last_edit_date: 1667719596,
[2022-11-07T00:40:01.068Z]       question_id: 74333655,
[2022-11-07T00:40:01.068Z]       content_license: 'CC BY-SA 4.0',
[2022-11-07T00:40:01.068Z]       link: 'https://stackoverflow.com/questions/74333655/how-do-i-use-foreign-key-and-primary-key-with-azure-sql',
[2022-11-07T00:40:01.068Z]       title: 'How do I use foreign key and primary key with azure sql'
[2022-11-07T00:40:01.068Z]     }
[2022-11-07T00:40:01.068Z]   ],
[2022-11-07T00:40:01.068Z]   has_more: false,
[2022-11-07T00:40:01.068Z]   quota_max: 300,
[2022-11-07T00:40:01.068Z]   quota_remaining: 299
[2022-11-07T00:40:01.068Z] }
[2022-11-07T00:40:01.091Z] Executed 'Functions.question-fetcher2' (Succeeded, Id=f330c993-9431-4528-b86d-24d8a5410845, Duration=1046ms)

取得した質問を CosmosDB に保存する

quetion-fetcher/index.ts
import axios from "axios";
import { AzureFunction, Context } from "@azure/functions";
import { initDB } from "../lib/db-config.js";
import { QuestionItem } from "../lib/QuestionItem.js";

const db = initDB();

const timerTrigger: AzureFunction = async function (
  context: Context,
  myTimer: any
): Promise<void> {
  const yesterday = new Date();
  yesterday.setDate(yesterday.getDate() - 1);
  const yesterdayEpoch = Math.floor(yesterday.getTime() / 1000);
  const todayEpoch = Math.floor(new Date().getTime() / 1000);
  const searchQuery = "azure";
  const endpoint = `https://api.stackexchange.com/2.3/search?page=1&pagesize=10&fromdate=${yesterdayEpoch}&todate=${todayEpoch}&order=desc&sort=creation&tagged=${searchQuery}&intitle=${searchQuery}&site=stackoverflow`;
  const response = await axios.get(endpoint);
  const { container } = await db;
  const questions = response.data?.items as QuestionItem[];
  await Promise.all(
    questions.map(
      (item) => item.answer_count === 0 && container.items.create(item)
    )
  );
};

export default timerTrigger;
lib/db-config.ts
import { CosmosClient } from "@azure/cosmos";

const cosmosConfig = {
  endpoint: process.env.COSMOSDB_URI,
  primaryKey: process.env.COSMOSDB_PRIMARY_KEY,
  database: process.env.COSMOSDB_DATABASE,
  container: process.env.COSMOSDB_CONTAINER,
  partitionKey: {
    paths: ["/id"],
  },
};

export const initDB = async () => {
  const cosmosClient = new CosmosClient({
    endpoint: cosmosConfig.endpoint!,
    key: cosmosConfig.primaryKey!,
  });
  const { database } = await cosmosClient.databases.createIfNotExists({
    id: cosmosConfig.database,
  });

  const { container } = await database.containers.createIfNotExists({
    id: cosmosConfig.container,
    partitionKey: cosmosConfig.partitionKey,
  });

  return {
    cosmosClient,
    database,
    container,
  };
};

lib/QuestionItem.ts
export interface QuestionItem {
  id?: string;
  tags: string[];
  owner: {
    account_id: number;
    reputation: number;
    user_id: number;
    user_type: string;
    accept_rate: number;
    profile_image: string;
    display_name: string;
    link: string;
  };
  is_answered: boolean;
  view_count: number;
  accepted_answer_id: number;
  answer_count: number;
  score: number;
  last_activity_date: number;
  creation_date: number;
  question_id: number;
  content_license: string;
  link: string;
  title: string;
}

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true", # <= 追加
    "COSMOSDB_URI": "https://db27c57c-0ee0-4-231-b9ee.documents.azure.com:443/", # <= 追加
    "COSMOSDB_PRIMARY_KEY": "kFVTjNGfPvSNsBLCWbVMqqpWPVmkWkd9bGLqpIi04kF1AXPrYMifOfSXqqPbgC4thAejkAj4bZ0Xgagh6PXrmA==", # <= 追加
    "COSMOSDB_DATABASE": "stackover_questions", # <= 追加
    "COSMOSDB_CONTAINER": "questions" # <= 追加
  }
}

Slack API notifier (Azure Cosmos DB trigger)の作成

Cosmos DB にデータが追加されると、この function が実行されます。実際にデータベースに追加されたレコードが Change Feed として送られてくるので、そのデータを slack のチャンネルに送信します。

npx func new --name slack-notifier --template "Azure Cosmos DB trigger" --language typescript
slack-notifier/index.ts
import { AzureFunction, Context } from "@azure/functions";
import axios from "axios";
import { QuestionItem } from "../lib/QuestionItem.js";

const SLACK_ENDPOINT =
  "https://hooks.slack.com/services/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";

const cosmosDBTrigger: AzureFunction = async function (
  context: Context,
  documents: QuestionItem[]
): Promise<void> {
  if (!!documents && documents.length > 0) {
    await axios
      .post(SLACK_ENDPOINT, { text: makeSlackMessage(documents) })
      .catch((err) => context.log(err));
  }
};

function makeSlackMessage(documents: QuestionItem[]) {
  const formatted = documents.map((docItem) => {
    return `title: ${docItem.title}\nURL: ${docItem.link}\nanswer count: ${
      docItem.answer_count
    }\ncreated: ${docItem.creation_date}\nviews: ${
      docItem.view_count
    }\nstatus: ${docItem.is_answered ? "answered" : "not yet answered"}\n`;
  });
  return formatted.join("\n");
}

export default cosmosDBTrigger;

function.jsonは以下のように編集する。bindings[0].connectionStringSettinglocal.settings.jsonで設定されているCOSMOSDB_CONNECTION_STRING_DOCUMENTDBを参照している。詳しくは以下の記事で説明している。

slack-notifer/function.json
{
  "bindings": [
    {
      "type": "cosmosDBTrigger",
      "name": "documents",
      "direction": "in",
      "leaseCollectionName": "leases",
      "connectionStringSetting": "COSMOSDB_CONNECTION_STRING_DOCUMENTDB", # <= 追加
      "databaseName": "stackover_questions", # <= 追加
      "collectionName": "questions", # <= 追加
      "createLeaseCollectionIfNotExists": true
    }
  ],
  "scriptFile": "../dist/slack-notifier/index.js"
}

COSMOSDB_CONNECTION_STRING_DOCUMENTDBの値は、COSMOSDB_URICOSMOSDB_PRIMARY_KEYの組み合わせ。

local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "COSMOSDB_URI": "https://db27c57c-0ee0-4-231-b9ee.documents.azure.com:443/",
    "COSMOSDB_PRIMARY_KEY": "kFVTjNGfPvSNsBLCWbVMqqpWPVmkWkd9bGLqpIi04kF1AXPrYMifOfSXqqPbgC4thAejkAj4bZ0Xgagh6PXrmA==",
    "COSMOSDB_DATABASE": "stackover_questions",
    "COSMOSDB_CONTAINER": "questions",
    # これ
    "COSMOSDB_CONNECTION_STRING_DOCUMENTDB": "AccountEndpoint=https://db27c57c-0ee0-4-231-b9ee.documents.azure.com:443/;AccountKey=kFVTjNGfPvSNsBLCWbVMqqpWPVmkWkd9bGLqpIi04kF1AXPrYMifOfSXqqPbgC4thAejkAj4bZ0Xgagh6PXrmA==;"
  }
}

REST API (HTTP trigger)の作成

npx func new --name get-questions --template 'HTTP trigger' --authlevel 'anonymous' --language typescript
get-questions/index.ts
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { initDB } from "../lib/db-config.js";

const db = initDB();

const httpTrigger: AzureFunction = async function (
  context: Context,
  req: HttpRequest
): Promise<void> {
  const { container } = await db;
  const { resources } = await container.items.readAll().fetchAll();

  context.res = {
    status: 200,
    body: resources,
  };
};

export default httpTrigger;
{
  "bindings": [
    {
      "authLevel": "Anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get"], # <= 編集
      "route": "questions" # <= 追加
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ],
  "scriptFile": "../dist/get-questions/index.js"
}

完成品を実行してみる

❯ npm run start

> stackover-questions@1.0.0 prestart
> npm run build


> stackover-questions@1.0.0 build
> tsc


> stackover-questions@1.0.0 start
> func start


Azure Functions Core Tools
Core Tools Version:       4.0.4829 Commit hash: N/A  (64-bit)
Function Runtime Version: 4.11.2.19273

[2022-11-07T05:58:12.394Z] Worker process started and initialized.

Functions:

        get-questions: [GET] http://localhost:7071/api/questions

        question-fetcher: timerTrigger

        slack-notifier: cosmosDBTrigger

しっかり、slackチャンネルにstack overflowの質問が送信されました。
MicrosoftTeams-image.png

REST APIからもデータベースに保存されているレコードを取得することができました。
Screen Shot 2022-11-07 at 15.03.25.png

おわりに

これで毎日ぼくのslackにazureに関するstack overflowの質問が届くようになりました。いつか必ず一番に回答してやろうと思います。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?