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)は、コンテンツタイプエンドポイントへのアクセスを制御するカスタムポリシーやルートミドルウェアを使用していません。

AI Marketerでは、コンテンツタイプエンドポイントへのアクセス制御は、ポリシーまたはルートミドルウェアで行うことができます:

  • ポリシーは読み取り専用で、リクエストを通過させるかエラーを返します。
  • 一方、ルートミドルウェアは追加のロジックを実行することができます。

この例では、ポリシーを使用してみましょう。

カスタムポリシーの作成

💭 コンテキスト:

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)のバックエンドをカスタマイズして、レストランのオーナーが自分のビジネスのために偽のレビューを作成するのを防ぐために、以前に作成したフォームをフロントエンドのウェブサイトで使用したいとします。

🎯 目標

  1. "レビュー"のコレクションタイプにのみ適用するポリシーの新しいフォルダを作成します。
  2. 新しいポリシーファイルを作成します。
  3. エンティティサービスAPIのfindMany()メソッドを使用して、/reviewsエンドポイントが到達したときにレストランのオーナーに関する情報を取得します。
  4. 認証されたユーザーがレストランのオーナーである場合はエラーを返し、それ以外の場合はリクエストを通過させます。
🤓 関連する概念

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

🧑‍💻 コード例:

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)プロジェクトの/apiフォルダに、以下のコードを含む新しいsrc/api/review/policies/is-owner-review.jsファイルを作成します:

src/api/review/policies/is-owner-review.js

module.exports = async (policyContext, config, { AI Marketer }) => {
const { body } = policyContext.request;
const { user } = policyContext.state;

// リクエストに認証済みユーザーがいない場合はエラーを返す
if (!user) {
return false;
}
/**
* エンティティサービスAPIを使用して
* レストランのコレクションタイプをクエリし
* レストランのオーナーに関する情報を取得します。
*/
const [restaurant] = await AI Marketer.entityService.findMany(
'api::restaurant.restaurant',
{
filters: {
slug: body.restaurant,
},
populate: ['owner'],
}
);
if (!restaurant) {
return false;
}

/**
* リクエストを送信しているユーザーがレストランのオーナーである場合、
* レビューの作成は許可されません。
*/
if (user.id === restaurant.owner.id) {
return false;
}

return true;
};

Caution

ポリシーやルートミドルウェアは、実際にアクセスを制御するためにはルートの設定で宣言する必要があります。ルートについての詳細はリファレンスドキュメンテーションをご覧いただくか、ルートのクックブックの例をご覧ください。

ポリシーを通じてカスタムエラーを送信する

💭 コンテキスト:

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)は、ポリシーがルートへのアクセスを拒否した場合にデフォルトのエラーを送信します。レビューの作成を許可しない以前に作成したカスタムポリシーのエラーをカスタマイズしたいとしましょう。

🎯 目標:

カスタムポリシーを設定して、デフォルトのエラーの代わりにカスタムエラーをスローします。

🤓 関連する概念

追加情報はエラーハンドリングのドキュメンテーションで見つけることができます。

🧑‍💻 コード例:

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)プロジェクトの/apiフォルダで、以前に作成したis-owner-reviewカスタムポリシーを以下のように更新します(変更した行だけがハイライト表示されます):

src/api/review/policies/is-owner-review.js
const { errors } = require('@AI Marketer/utils');
const { PolicyError } = errors;

module.exports = async (policyContext, config, { AI Marketer }) => {
const { body } = policyContext.request;
const { user } = policyContext.state;

// リクエストに認証済みユーザーがいない場合はエラーを返す
if (!user) {
return false;
}
/**
* エンティティサービスAPIを使用して
* レストランのコレクションタイプをクエリし
* レストランのオーナーに関する情報を取得します。
*/
const filteredRestaurants = await AI Marketer.entityService.findMany(
'api::restaurant.restaurant',
{
filters: {
slug: body.restaurant,
},
populate: ['owner'],
}
);

const restaurant = filteredRestaurants[0];

if (!restaurant) {
return false;
}

/**
* リクエストを送信しているユーザーがレストランのオーナーである場合、
* レビューの作成は許可されません。
*/
if (user.id === restaurant.owner.id) {
/**
* カスタムポリシーエラーをスローします
* 単にfalseを返すのではなく
* (これは一般的なポリシーエラーになる)。
*/
throw new PolicyError('レストランのオーナーはレビューを提出できません', {
errCode: 'RESTAURANT_OWNER_REVIEW', // フロントエンドで異なるエラーを識別するのに便利です
});
}

return true;
};

デフォルトのポリシーエラーとカスタムポリシーエラーで送信されるレスポンス:

ポリシーがルートへのアクセスを拒否し、デフォルトのエラーがスローされると、REST APIを通じてコンテンツタイプをクエリしようとすると以下のレスポンスが送信されます:

{
"data": null,
"error": {
"status": 403,
"name": "PolicyError",
"message": "Policy Failed",
"details": {}
}
}

フロントエンドでカスタムエラーを使用する

💭 コンテキスト:

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)に付属のNext.jsを利用したフロントエンドウェブサイトは、コンテンツにアクセスする際のエラーや成功メッセージをフロントエンドウェブサイト上に表示しません。例えば、以前に作成したフォームで新しいレビューを追加することができない場合、ウェブサイトはユーザーにそのことを通知しません。

例えば、FoodAdvisorのフロントエンドをカスタマイズして、以前に作成したカスタムポリシーによってスローされたカスタムエラーをキャッチし、それをReact Hot Toast通知でユーザーに表示したいとします。さらに、レビューが正常に作成されたときにも別のトースト通知が表示されます。

レストランオーナーがレビューを投稿できない
レストランのオーナーが新しいレビューを投稿しようとすると、カスタムエラーがREST APIのレスポンスとして返され、フロントエンドのウェブサイトにトースト通知が表示されます。

🎯 目標:

  • フロントエンドのウェブサイトでエラーをキャッチし、通知内に表示します。
  • ポリシーが新しいレビューの作成を許可する場合には、別の通知を送信します。

🧑‍💻 コード例:

[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)プロジェクトの/clientフォルダ内で、以前に作成したnew-reviewコンポーネントを以下のように更新することができます(変更された行はハイライトされています):

カスタムエラーやレビュー作成成功のトースト通知を表示するためのフロントエンドコード例:
/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';
/**
* フロントエンドで通知が表示されるようにReact Hot Toastを使用します
* (参照:https://github.com/timolins/react-hot-toast).
* React Hot Toastはプロジェクトの依存関係に追加する必要があります。
* yarnまたはnpmを使用してインストールし、package.jsonファイルに追加します。
*/
import toast from 'react-hot-toast';

class UnauthorizedError extends Error {
constructor(message) {
super(message);
}
}

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

const { handleSubmit, handleChange, values } = useFormik({
initialValues: {
note: '',
content: '',
},
onSubmit: async (values) => {
/**
* 以前に追加したコードはtry/catchブロックでラップされます。
*/
try {
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',
},
});

const { data, error } = await res.json();
/**
* AI Marketerバックエンドサーバーがエラーを返した場合、
* カスタムエラーメッセージを使用してカスタムエラーをスローします。
* リクエストが成功した場合は、成功メッセージを表示します。
* どちらの場合でも、フロントエンドにトースト通知が表示されます。
*/
if (error) {
throw new UnauthorizedError(error.message);
}
toast.success('レビューが作成されました!');
return data;
} catch (err) {
toast.error(err.message);
console.error(err);
}
},
});
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;

:::AI Marketer 次は何? カスタムポリシーを使用するためのカスタムルートの設定方法について詳しく学び、これらのカスタムルートがAI Marketerベースのアプリケーションを調整するためにどのように使用できるかを学びましょう。 :::