Open7

React + Go でマンガの新刊情報の通知・一覧表示をする Web サービスを作ろうとしている人の作業ログ

ピン留めされたアイテム

このスクラップの内容について

React の勉強を兼ねて、マンガの新刊情報を一覧表示したり、お気に入りに登録したシリーズや著者の新刊を通知してくれたりするWebサービスを作ろうとしています。

このスクラップでは、上記の作業を通して考えたことや調べたことをメモしていきます。

現状はこんな感じ。最近発売されたマンガをタイル形式で一覧表示するところまではできています。新刊情報の取得には openBD (https://openbd.jp/) のAPI を使用しています。

使っている技術

  • PostgreSQL
    • openBD から取得した書誌情報を蓄積するのに使用
    • 今後はユーザの登録した情報を保存するテーブルを追加していく
  • Go
    • opendBD の API を叩いて書誌情報を取得するのに使用
    • cron で毎日決まった時間に実行して DB を更新するようにしている
    • 最初は Python で書いていたけど、速度の遅さが気になってきたので Go で書き直した
  • Express
    • 上記の DB から書誌情報を検索して返す API サーバ を作成するのに使用
  • React + Material UI
    • フロントエンド ←今はここを勉強中

次にやるべきことはなんだろうか?

  • ユーザの認証機能を追加する
    • IDaaS の Auth0 を使う予定
    • ここからお気に入りのシリーズや著者の登録、そして通知機能につながっていく
  • 未実装のページを(見た目だけ)実装する
    • お気に入りのシリーズや著者の管理ページ
    • タイトルや著者、発売日などでマンガを検索できる詳細検索ページ
    • おしらせページ

機能を優先してユーザ認証を実装するか、見た目を優先して画面だけ作っていくか、どっちだろう?

それとも、サービスをどんな人にどんな風に使ってもらいたいとか、そういう理論を先にしっかり考えておくべきかも?

Auth0 でユーザ認証の実装

Auth0 によるユーザ認証機能を実装していくことにします。

基本的には、Auth0 React SDK Quickstarts: Login を見れば簡単に React アプリケーションでのユーザ認証が実装できます。

今作っているアプリでは Material-UI を使って UI を作成しているので、上記ページに載っているログインボタンとログアウトボタンのサンプルを Material-UI の Button コンポーネント を使うように書き換えました。

ログインボタン

LoginButton.js
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Button } from "@material-ui/core";

const LoginButton = () => {
  const { loginWithRedirect } = useAuth0();

  return (
    <Button
      variant="contained"
      color="default"
      onClick={() => {
        loginWithRedirect();
      }}
    >
      Log in
    </Button>
  );
};

export default LoginButton;

ログアウトボタン

LogoutButton.js
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Button } from "@material-ui/core";

const LogoutButton = () => {
  const { logout } = useAuth0();

  return (
    <Button
      variant="contained"
      color="inherit"
      onClick={() => {
        logout({ returnTo: window.location.origin });
      }}
    >
      Log out
    </Button>
  );
};

export default LogoutButton;

動作イメージ

こんな感じで動くようになりました。

Auth0 で取得したユーザの情報を Material-UI で表示

Auth0 で取得したユーザの情報(ユーザ名、メールアドレス、アバター画像)を Material-UI を使って表示してみます。

アカウントメニューの作成

Material-UI の AvatarMenu コンポーネントを使って、ユーザがログインしているときのメニューを表示する AccountMenu というコンポーネントを作りました。

AcountMenu.js
import React from 'react';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import { Avatar } from '@material-ui/core';
import LogoutButton from "./LogoutButton";

export default function AccountMenu({name, email, avater}) {
  const [anchorEl, setAnchorEl] = React.useState(null);

  const handleClick = (event) => {
    setAnchorEl(event.currentTarget);
  };

  const handleClose = () => {
    setAnchorEl(null);
  };

  return (
    <div>
      <Avatar alt={name} src={avater} aria-controls="account-menu" aria-haspopup="true" onClick={handleClick} />
      <Menu
        id="account-menu"
        anchorEl={anchorEl}
        keepMounted
        open={Boolean(anchorEl)}
        onClose={handleClose}
      >
        <MenuItem><Avatar alt={name} src={avater} /> {name} ({email})</MenuItem>
        <MenuItem onClick={handleClose}>設定</MenuItem>
        <MenuItem onClick={handleClose}><LogoutButton /></MenuItem>
      </Menu>
    </div>
  );
}

アバター画像が表示され、クリックするとアカウントの情報と設定画面へのリンク、ログアウトボタンが表示されます。

Image from Gyazo

Image from Gyazo

ログイン状態に応じて表示を切り替え

ログイン状態に応じて、ログインしていなかったらログインボタンを表示し、ログインしていたら上記のアカウントメニューを表示するようにしてみます。

ユーザがログインしているかどうかは、Auth0 の isAuthenticated プロパティで判断できるようなので、その値に応じてレンダリングするコンポーネントを変化させればよさそうです。

LoginButtonOrAccountMenu.js
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import AccountMenu from "./AccountMenu";
import LoginButton from "./LoginButton";

// ログイン状態に応じてログインボタンまたはアカウントメニューを表示する
const LoginButtonOrAccountMenu = () => {
  const { user, isAuthenticated, isLoading } = useAuth0();

  if (isLoading) {
    return <div>Loading ...</div>;
  }
  if (isAuthenticated) {
    return (
      <AccountMenu name={user.name} email={user.email} avater={user.picture} />
    );
  } else {
    return <LoginButton />;
  }
};

export default LoginButtonOrAccountMenu;

ログインしていない状態からログインしたときの様子

Image from Gyazo

ログインしている状態からログアウトしたときの様子

Image from Gyazo

Auth0 の API 呼び出し

Auth0 React SDK Quickstarts: Call an API にしたがって Auth0 の API を呼び出し、ユーザのプロフィールを表示するコンポーネントを書きました。

Profile.js
import React, { useEffect, useState } from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Avatar } from "@material-ui/core";

const Profile = () => {
  const {
    user,
    isAuthenticated,
    isLoading,
    getAccessTokenSilently,
  } = useAuth0();
  const [userMetadata, setUserMetadata] = useState(null);

  useEffect(() => {
    const getUserMetadata = async () => {
      const domain = "YOUR_DOMAIN";

      try {
        const accessToken = await getAccessTokenSilently({
          audience: `https://${domain}/api/v2/`,
          scope: "read:current_user",
        });

        const userDetailsByIdUrl = `https://${domain}/api/v2/users/${user.sub}`;

        const metadataResponse = await fetch(userDetailsByIdUrl, {
          headers: {
            Authorization: `Bearer ${accessToken}`,
          },
        });

        const { user_metadata } = await metadataResponse.json();

        setUserMetadata(user_metadata);
      } catch (e) {
        console.log(e.message);
      }
    };

    getUserMetadata();
  }, []);

  if (isLoading) {
    return <div>Loading ...</div>;
  }

  return (
    isAuthenticated && (
      <div>
        <Avatar alt={user.name} src={user.picture} />
        <h2>{user.name}</h2>
        <p>{user.email}</p>
        <h3>User Metadata</h3>
        {userMetadata ? (
          <pre>{JSON.stringify(userMetadata, null, 2)}</pre>
        ) : (
          "No user metadata defined"
        )}
      </div>
    )
  );
};

export default Profile;

テストユーザの作成

コードの動作確認用にテストユーザーを作成します。

Auth0 のダッシュボードで Users のタブを選択し、右上の「Create Users」でユーザを作成

その後、作成されたユーザの Metadata を編集

前回の投稿で作成した AccountMenu コンポーネントに組み込んで、ユーザのプロフィールが表示されるようになりました。

Image from Gyazo

作成者以外のコメントは許可されていません