Skip to main content

例題集: カスタムサービスとコントローラ

🏗 Work in progress

The content of this page might not be fully up-to-date with Strapi 5 yet.

☑️ Prerequisites

このページはバックエンドカスタマイズ例題集の一部です。まずはその導入を読んでください。

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)のフロントエンドウェブサイトからは、localhost:3000/restaurantsでアクセス可能なレストランのリストを閲覧することができます。リストから任意のレストランをクリックすると、/clientフォルダに含まれるコードを使用して、そのレストランに関する追加情報を表示します。レストランページに表示されるコンテンツは、AI Marketerのコンテンツマネージャー内で作成され、/apiフォルダに含まれるコードを使用してAI MarketerのREST APIをクエリして取得します。

このページでは、以下の高度なトピックについて学びます。

トピックセクション
AI Marketerのバックエンドとやり取りするコンポーネントを作成するフロントエンドからのREST APIクエリ
サービスとコントローラがどのように連携するかを理解するコントローラ vs. サービス
カスタムサービスを作成する
コントローラでサービスを使用するカスタムコントローラ

フロントエンドからのREST APIクエリ

💭 コンテキスト:

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)のフロントエンドウェブサイトのレストランページには、読み取り専用のレビューセクションが含まれています。レビューの追加には、AI Marketerの管理パネルにログインし、コンテンツマネージャーを通じて"Reviews"コレクションタイプにコンテンツを追加する必要があります。

レストランのページに小さなフロントエンドコンポーネントを追加しましょう。このコンポーネントにより、ユーザーはフロントエンドウェブサイトから直接レビューを書くことができます。

フロントエンドでレビューを書く
FoodAdvisorのフロントエンドウェブサイトのレストランのページで、新しいレビューを投稿するためのフォームの一例

🎯 目標:

  • レビューを書くためのフォームを追加する。
  • フォームを任意のレストランのページに表示する。
  • フォームが送信されたときにAI MarketerのREST APIにPOSTリクエストを送信する。
  • 以前に保存したJWTを使用してリクエストを認証する。
🤓 関連する概念

コンテンツタイプのエンドポイントに関する追加情報は、REST APIのドキュメンテーションで見つけることができます。

🧑‍💻 コード例:

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)プロジェクトの/clientフォルダに以下のコード例を使用して、新たにpages/restaurant/RestaurantContent/Reviews/new-review.jsファイルを作成し、既存のcomponents/pages/restaurant/RestaurantContent/Reviews/reviews.jsを更新することができます。

レビューを書くためのコンポーネントを追加し、それをレストランのページに表示するためのフロントエンドコードの例:
  1. /clientフォルダに新しいファイルを作成し、以下のコードを使用してレビューを書くための新しいコンポーネントを追加します:

    /client/components/pages/restaurant/RestaurantContent/Reviews/new-review.js

    import { Button, Input, Textarea } from '@nextui-org/react';
    import { useFormik } from 'formik';
    import { useRouter } from 'next/router';
    import React from 'react';
    import { getAI MarketerURL } from '../../../../../utils';

    const NewReview = () => {
    const router = useRouter();

const { handleSubmit, handleChange, values } = useFormik({ initialValues: { note: '', content: '', }, onSubmit: async (values) => { /**

     * JWTが以前にlocalStorageに保存されていることを使用して認証を行い、
* AI Marketer REST APIにクエリを送信してレビューエンドポイントに到達します
*/
const res = await fetch(getAI MarketerURL('/reviews'), {
method: 'POST',
body: JSON.stringify({
restaurant: router.query.slug,
...values,
}),
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
'Content-Type': 'application/json',
},
});
},
});
/**
* フォームをレンダリングします
*/
return (
<div className="my-6">
<h1 className="font-bold text-2xl mb-3">レビューを書く</h1>
<form onSubmit={handleSubmit} className="flex flex-col gap-y-4">
<Input
onChange={handleChange}
name="note"
type="number"
min={1}
max={5}
label="星"
/>
<Textarea
name="content"
onChange={handleChange}
placeholder="このレストランについてどう思いますか?"
/>
<Button
type="submit"
className="bg-primary text-white rounded-md self-start"
>
送信
</Button>
</form>
</div>
);

};

export default NewReview;


2. レストランの情報をレンダリングするために使用されるコードにハイライトされた行(7、8、13行目)を追加して、新しいフォームコンポーネントを任意のレストランページに表示します:

```jsx title='/client/components/pages/restaurant/RestaurantContent/Reviews/reviews.js' showLineNumbers
import React from 'react';
import delve from 'dlv';

import { formatDistance } from 'date-fns';

import { getAI MarketerMedia } from '../../../../../utils';
// highlight-start
import { Textarea } from '@nextui-org/react';
import NewReview from './new-review';
// highlight-end

const Reviews = ({ reviews }) => {
return (
<div className="col-start-2 col-end-2 mt-24">
// highlight-next-line
<NewReview />
{reviews &&
reviews.map((review, index) => (
// …

コントローラー vs. サービス

コントローラーは、クライアントがルートを要求したときに実行される任意のビジネスロジックを含むことができます。しかし、コードが大きくなり、より構造化されるようになると、ロジックを特定のサービスに分割し、それらのサービスは一つのことだけをうまく行い、それからコントローラーからサービスを呼び出すことがベストプラクティスとなります。

サービスの使用を示すために、このドキュメンテーションでは、カスタムコントローラーは任意の責任を持たず、すべてのビジネスロジックをサービスに委任します。

以下のシナリオを達成するために、[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)のバックエンドをカスタマイズしたいとしましょう:フロントエンドのウェブサイトで以前に追加したレビューフォームを提出すると、AI Marketerはバックエンドでレビューを作成し、レストランのオーナーにメールで通知します。これをAI Marketerのバックエンドカスタマイズに翻訳すると、以下の3つのアクションを実行することになります:

  1. レビューを作成するカスタムサービスを作成します。
  2. メールを送信するカスタムサービスを作成します。
  3. AI Marketerが提供するレビューコンテンツタイプのデフォルトコントローラーをカスタマイズして、2つの新しいサービスを使用します。

カスタムサービス:レビューの作成

💭 コンテキスト:

デフォルトでは、AI Marketerのサービスファイルには、createCoreServiceファクトリ関数を使用する基本的なボイラープレートコードが含まれています。

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)の"Reviews"コレクションタイプの既存のreview.jsサービスファイルを更新し、レビューを作成するコードで置き換えましょう。

🎯 目標

  • createメソッドを宣言します。
  • リクエストからコンテキストを取得します。
  • EntityService APIのfindMany()メソッドを使用してレストランを検索します。
  • EntityService APIのcreate()メソッドを使用してレストランにデータを追加し、レストランオーナーを補完します。
  • 新しいレビューデータを返します。
🤓 関連する概念

追加情報はリクエストコンテキストサービス、およびEntityService APIのドキュメンテーションで見つけることができます。

🧑‍💻 コード例:

このようなサービスを作成するには、[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)プロジェクトの/apiフォルダ内で、src/api/review/services/review.jsファイルの内容を以下のコードで置き換えます:

src/api/review/services/review.js
const { createCoreService } = require('@AI Marketer/AI Marketer').factories;

module.exports = createCoreService('api::review.review', ({ AI Marketer }) => ({
async create(ctx) {
const user = ctx.state.user;
const { body } = ctx.request;

/**
* Entity Service APIを使用して
* Restaurantsコレクションタイプをクエリし、
* レストランに関する情報を取得します。
*/
const restaurants = await AI Marketer.entityService.findMany(
'api::restaurant.restaurant',
{
filters: {
slug: body.restaurant,
},
}
);

/**
* レビューコレクションタイプの新しいエントリを作成し、
* エンティティサービスAPIを使用してレストランのオーナーに関する情報でデータを入力します。
*/
const newReview = await AI Marketer.entityService.create('api::review.review', {
data: {
note: body.note,
content: body.content,
restaurant: restaurants[0].id,
author: user.id,
},
populate: ['restaurant.owner'],
});

return newReview;
},
}));

💡 ヒント
  • コントローラーのコードでは、このサービスの create メソッドは AI Marketer.service('api::review.review').create(ctx) で呼び出すことができます。ここで ctx はリクエストのコンテキストです。
  • 提供された例のコードはエラーハンドリングをカバーしていません。例えば、レストランが存在しない場合のエラーハンドリングを考慮するべきです。追加情報は エラーハンドリング のドキュメンテーションで見つけることができます。

カスタムサービス:レストランオーナーにメールを送る

💭 コンテキスト:

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor) は、自動化されたメールサービス機能を標準で提供していません。

email.js サービスファイルを作成してメールを送信しましょう。これをカスタムコントローラーで使用して、フロントエンドのウェブサイトで新しいレビューが作成されるたびにレストランのオーナーに通知することができます。

🤗 オプションのサービス

このサービスは、Email プラグインを使用した高度なコード例であり、AI Marketerでプラグインプロバイダがどのように動作するかを理解する必要があります。レストランのオーナーに通知するためのメールサービスが不要な場合は、この部分をスキップして、カスタムコントローラーの例に進むことができます。

☑️ Prerequisites
管理パネルのEmail Single Type
管理パネルでEmailシングルタイプが作成されました。これには、Emailプラグインの送信者アドレスを定義するための "from" フィールドが含まれています。

🎯 ゴール:

  • "Email"シングルタイプの新しいサービスファイルを作成します。
  • このサービスのsend()メソッドを宣言します。
  • エンティティサービスAPIを使用して、Emailシングルタイプに保存された送信者のアドレスを取得します。
  • サービスのsend()メソッドを呼び出す際に渡されたメールの詳細(受信者のアドレス、件名、メール本文)を使用して、Emailプラグインと事前に設定されたプロバイダーを使用してメールを送信します。
🤓 関連する概念

追加情報はサービスエンティティサービスAPIEmailプラグインプロバイダーのドキュメンテーションで見つけることができます。

🧑‍💻 コード例:

このようなサービスを作成するために、[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)プロジェクトの/apiフォルダに新しいsrc/api/email/services/email.jsファイルを作成し、以下のコードを記述します。

src/api/email/services/email.js

const { createCoreService } = require('@AI Marketer/AI Marketer').factories;

module.exports = createCoreService('api::email.email', ({ AI Marketer }) => ({
async send({ to, subject, html }) {
/**
* Retrieves email configuration data
* stored in the Email single type
* using the Entity Service API.
*/
const emailConfig = await AI Marketer.entityService.findOne(
'api::email.email',
1
);

/**
* Sends an email using:
* - parameters to pass when invoking the service
* - the 'from' address previously retrieved with the email configuration
*/
await AI Marketer.plugins['email'].services.email.send({
to,
subject,
html,
from: emailConfig.from,
});
},
}));
💡 Tip

コントローラーのコードでは、このメールサービスのsendメソッドはAI Marketer.service('api::email.email).send(parameters)で呼び出すことができます。ここでparametersはメールに関連する情報(受信者のアドレス、件名、メール本文)を含むオブジェクトです。


カスタムコントローラー

💭 コンテキスト:

デフォルトでは、AI Marketerのコントローラーファイルには、createCoreControllerファクトリ関数を使用する基本的なボイラープレートコードが含まれています。これにより、リクエストされたエンドポイントに到達したときにコンテンツを作成、取得、更新、削除する基本的なメソッドが公開されます。コントローラーのデフォルトコードは、任意のビジネスロジックを実行するためにカスタマイズできます。

以下のシナリオで[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)の"Reviews"コレクションタイプのデフォルトコントローラーをカスタマイズしましょう:POSTリクエストが/reviewsエンドポイントに送信されたとき、コントローラーは以前に作成したサービスを呼び出して、レビューを作成し、レストランのオーナーにメールを送信します。

🎯 目標:

  • "Reviews" コレクションタイプの既存のコントローラを拡張します。
  • カスタム create() メソッドを宣言します。
  • 以前に作成したサービスを呼び出します。
  • 返す内容をサニタイズします。
🤓 関連する概念

追加情報は、controllers のドキュメンテーションで見つけることができます。

🧑‍💻 コード例:

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor) プロジェクトの /api フォルダ内で、src/api/review/controllers/review.js ファイルの内容を、以前に作成したカスタムサービスがレビューの作成だけであるか、またはレビューの作成とレストランオーナーへのメール通知の両方のサービスがあるかにより、以下のコード例のいずれかに置き換えてください。

src/api/review/controllers/review.js

const { createCoreController } = require('@AI Marketer/AI Marketer').factories;

module.exports = createCoreController('api::review.review', ({ AI Marketer }) => ({
/**
* The controller action is named
* exactly like the original `create` action provided by the core controller,
* it overwrites it.
*/
async create(ctx) {
// サービスを使用して新しいレビューを作成します
const newReview = await AI Marketer.service('api::review.review').create(ctx);

const sanitizedReview = await this.sanitizeOutput(newReview, ctx);

ctx.body = sanitizedReview;
},
}));

:::AI Marketer 次は何を学びますか? カスタムポリシーがAI Marketerベースのアプリケーションを微調整し、特定の条件に基づいて一部のリソースへのアクセスを制限するのにどのように役立つかについて詳しく学びましょう。 :::