はじめに
azure functionを使って、サーバレスらしい小さなアプリケーションを作って見たかったので作ってみました。コンセプト的には、「azureに関するstack overflowの質問を一番に回答してやろう」的なやつです。そのための機能として、以下を実装していきます。
- 毎日stackoverflowのazureに関する質問を取得する
- 取得した質問をデータベースに保存する
- 質問をslackに送信する
- REST APIからデータベースに保存した質問を取得できる
ようするに、毎日最新の stack overflow の azure に関する質問を slack のチャンネルに送信してくれるアプリケーションです。実際に回答をするかはあなた次第です。あと一応データベースに保存してあるので、REST API も作っておきます。Web インターフェイスも欲しくなるかもしれないので。
アプリケーション自体は、すべてazure functionsとサードパーティーのAPIを使って開発します。サーバレス感が少しでもあるかな。
Slack API の準備
Slack アカウントの作成
さっそく、Slack のアカウントを作成していきます。完全無料なので気にせず作っちゃってください。
Web Hook アプリを追加
slack に Node.js からメッセージを飛ばすために、web hook を追加します。slackアプリとして簡単に追加できます。
Incoming Webhook アプリの追加先のチャンネルはどれでもいいです。好きなのを選んでください。
アプリの追加ができたら、Webhook URL
が発行されます。その URL を使うと簡単に slack にメッセージを飛ばす事ができます。
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
StackExchange API の準備
StackExchange API を使うと簡単に stack overflow の質問を取得することができます。もちろん無料です。
search GET
今回は、azure に関する質問を検索したいので、search GET
の エンドポイントを使います。
フォームに任意の値を入力するだけで、APIリクエストに必要なURLをビルドしてくれる。便利。
リクエスト内容は:
- azureに関する質問
- azureのタグ付けされた質問
- 1ページ目
- 1ページあたりの質問の数は10個
- 投稿日を基準にソート
- 順番はデサント
- タイトルにazureキーワードが入っている
Run
ボタンをクリックすると実査にリクエストを送ることができる。
生成された URL をどこかへメモしておこう。後で使います。
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 を使うよう設定する。
{
"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.json
の bindings[0].schedule
で設定できる。デフォルトは5分に一回実行されるように設定されている。
{
"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 の質問を取得する。
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;
fromdate
と todate
の フォーマットは unix epoch time なので 以下のコードでそれぞれ計算する。
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タイプ
実際に、 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'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 に保存する
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;
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,
};
};
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
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].connectionStringSetting
は local.settings.json
で設定されているCOSMOSDB_CONNECTION_STRING_DOCUMENTDB
を参照している。詳しくは以下の記事で説明している。
{
"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_URI
とCOSMOSDB_PRIMARY_KEY
の組み合わせ。
{
"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
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の質問が送信されました。
REST APIからもデータベースに保存されているレコードを取得することができました。
おわりに
これで毎日ぼくのslackにazureに関するstack overflowの質問が届くようになりました。いつか必ず一番に回答してやろうと思います。