Filamentで開発4(APIを実装)

概要

Laravelで認証済みのユーザーのみにAPIを提供したい場合。Filamentで開発4と書いてあるが、どちらかというとLaravelでの実装。

下調べ

filamentのログイン認証について

通常/admin/loginがログインページだが、これを別のURLでも表示したい場合routes/web.phpを編集する必要がある。下記のようにすれば/loginでログインページが表示される。

use Filament\Pages\Auth\Login as FilamentLogin;

Route::get('login', FilamentLogin::class);

Laravel10.xの認証について公式ドキュメント(日本語版)を読んで、得た断片的な知識をメモしていく。

  • userテーブルには既にremember_tokenカラムが入っている。

流れ

  1. 下準備の確認
    • Laravel-Permissionのインストールされているか?
    • UserモデルにHasRolesトレントが追加されているか?
    • テストに使用するユーザーやロール、権限などが作成されているか?
  2. API認証の実装
    • Laravel Sanctum(サンクタム)かPassport(パスポート)か?独自実装か?

API部分

コントローラーの作成

コントローラーファイルの作成

php artisan make:controller Gamer/ScoreController

以下のファイルが生成される

  • app\Http\Controllers\Gamer\ScoreController.php

コントローラーファイルの編集

<?php

namespace App\Http\Controllers\Gamer;

use App\Models\User;
use App\Models\Score;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class ScoreController extends Controller
{
    public function getUserName(Request $request) {

        if ($request->user()) {
            $response = response()->json(
                [
                    'id' => $request->user()->id,
                    'name' => $request->user()->name,
                    'guild_name' => $request->user()->guild->name,
                ],
                200,
                [], // 追加ヘッダー
                JSON_UNESCAPED_UNICODE // デバッグしやすいようにエスケープしない
            );
            return $response;
        }

        return response()->json(['error' => 'Not authenticated.'], 401);
    }
    public function saveScore(Request $request) {

        if ($request->user()) {

            $score = new Score();
            $score->user_id = $request->user()->id;
            $score->guild_id = $request->user()->guild_id;
            $score->score = $request->input('score');
            $score->stage_id = $request->input('stage_id');
            $score->save();

            return response()->json(['message' => 'Score saved successfully'], 200);
        }

        return response()->json(['message' => 'ERROR Unknown user'], 401);
    }
}

トラブル対応

UnityからのPostがうまく行かず、デバッグ用に使ったコードを残しておく。Postされたデータをstorage\app以下にファイル出力した。

use Illuminate\Support\Facades\Storage;
// ~~~~~中略~~~~~
    public function saveScore(Request $request) {
        Storage::put('request.txt', $request);

FormからはうまくいくのにUnityからはうまく行かなかったのはcontent-typeが異なったから。正解はapplication/x-www-form-urlencoded

// Form
// Content-Type:              application/x-www-form-urlencoded

// Unity
// Content-Type:              application/json

カーネルにミドルウェアを追加

apiコントローラーで認証関連を使いたいので、追記する。

    protected $middlewareGroups = [
        'web' => [
            // ~~~~~中略~~~~~
        ],

        'api' => [
            // ~~~~~中略~~~~~
            // 以下の4項目を追加
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        ],
    ];

apiファイルの編集

apiを呼び出すためのルートを追加。

ここで追加した項目は/api以下に設定される。

<?php

// ~~~~~中略~~~~~
use App\Http\Controllers\Gamer\ScoreController;
// ~~~~~中略~~~~~

Route::get('getUserName', [ScoreController::class, 'getUserName']);
Route::post('saveScore', [ScoreController::class, 'saveScore']);

呼び出し部分(簡易版)

簡易版

Postテスト用のHTML作成

<h1>テスト用フォーム</h1>
<form action="http://localhost/api/saveScore" method="POST">
  <input type="text" name="score" placeholder="score">
  <input type="text" name="stage_id" placeholder="stage_id">
  <input type="submit" value="送信">
</form>

Postテスト用のp5.js作成

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);
}

function mousePressed() {
  saveScore(frameCount);
  background(220,0,0);
}

function saveScore(scoreValue) {
    let url = 'http://localhost/api/saveScore';
    let data = {
        score: scoreValue,
        stage: 'サンプルステージ',
    };

    httpPost(url, 'json', data, function(response) {
        console.log(response);
    });
}

呼び出し部分(本命版)

コードをもらってくる

当初作りたかったUnity版。下記のサイトのクラスをそのまま使わせてもらった。

※念のためソースコード

ファイルの配置

UnityのAssets/Scripts/以下に上記のファイルを展開する。

Scripts
│  SceneScript.cs
│
└─Api
    ├─Base
    │      BaseApi.cs
    │      CoroutineHandler.cs
    │      UrlHelpers.cs
    │
    └─SampleApi
            GetWeatherApi.cs
            PostSampleApi.cs

GETを作る

雛形をコピーして編集する

GetWeatherApi.csをコピーしてGetUserNameApi.csを作り、内容を書き換える。

using System;

namespace Api
{
    /// <summary>
    /// ユーザー名を取得します
    /// </summary>
    public class GetUserNameApi : BaseApi
    {
        /// リクエストパラメータ
        [Serializable]
        public struct Request
        {
            // 
        }
        public Request request;

        /// レスポンスデータ
        [Serializable]
        public struct Response
        {
            // Jsonデータの名前に一致させる
            public string name;
            public string guild_name;
        }
        public Response response;

        // コンストラクタ
        public GetUserNameApi()
        {
            BaseUrl = "http://localhost/api";
            EndPoint = "/getUserName";
        }
    }
}

Scene用のスクリプトを編集する

    void Start()
    {
        Api.GetUserNameApi getApi = new Api.GetUserNameApi();
        getApi.Get<Api.GetUserNameApi.Request>(ref getApi.request, result =>
        {
            if (result.isSuccess)
            {
                // リクエストに成功した場合
                getApi.response = getApi.Response<Api.GetUserNameApi.Response>();
                Debug.Log("ユーザー名取得APIのリクエストに成功しました");
                Debug.Log(getApi.response.username);
                userNameText.GetComponent<Text>().text = getApi.response.username;
            }
            else
            {
                // リクエストに失敗した場合
                Debug.Log("ユーザー名取得APIのリクエストに失敗しました");
                Debug.Log(result.error);
                userNameText.GetComponent<Text>().text = "失敗";
            }
        });
    }

POSTを作る

PostSampleApi.csをコピーしてPostScoreApi.csを作り、内容を書き換える。

上の方でトラブった話をしたが、個々の部分。もとのPostSampleApi.csではcontent-typeをapplication/jsonとしていたが、これだと500エラーになってしまった。簡易版のFormと送られてきたデータを比較してapplication/x-www-form-urlencodedが正解だということに気づけた。

雛形をコピーして編集する

using System;

namespace Api
{
    /// <summary>
    ///Scoreを保存します
    /// </summary>
    public class PostScoreApi : BaseApi
    {
        /// リクエストパラメータ
        [Serializable]
        public struct Request
        {
            public string score;
            public string stage_id;
        }
        public Request request;

        /// レスポンスデータ
        [Serializable]
        public struct Response
        {
            public string message;
        }
        public Response response;

        // コンストラクタ
        public PostScoreApi(string score, string stage_id)
        {
            BaseUrl = "http://localhost/api";
            EndPoint = "/saveScore";
            Headers.Add("content-type", "application/x-www-form-urlencoded");
            request.score = score;
            request.stage_id = stage_id;
        }
    }
}

Scene用のスクリプトを編集する

    // ~~~~~中略~~~~~
    public static int stage_id = 0; // サーバー側に合わせるためのステージ番号
    // ~~~~~中略~~~~~
    void Update()
    {
        if (PlayerController.gameState == "gameclear")
        {

            string score = scoreText.GetComponent<Text>().text;
            stage_id += 1;
            Api.PostScoreApi postApi = new Api.PostScoreApi(score, stage_id.ToString());
            postApi.Post<Api.PostScoreApi.Request>(ref postApi.request, result =>
            {
                if (result.isSuccess)
                {
                    postApi.response = postApi.Response<Api.PostScoreApi.Response>();
                    Debug.Log("PostScoreApiのリクエストに成功しました");
                }
                else
                {
                    Debug.Log("PostScoreApiのリクエストに失敗しました");
                    Debug.Log(result.error);
                    Debug.Log(postApi.response.message);
                }
            });

認証部分(guard)よくわからないので保留中

データ更新時のリダイレクト先変更方法

データ更新時のリダイレクト先変更方法

データ更新時のリダイレクト先変更方法

データ更新時のリダイレクト先変更方法

データ更新時のリダイレクト先変更方法

コメントする