newt と Next.jsでHP作成してみる
概要
友達の設計事務所のHP作成をすることになりました。
コンテンツは非エンジニアでも更新できるようにCMSで作成しました。
NewtというヘッドレスCMSを初めて使ったのですが、Vercelでデプロイするまで、簡単に作れたので内容をまとめます
CMSの選定
以上から、Newtを選択することにしました!
設計 / Next.jsでフロント部分を実装する
HPでは、Home, About, Works, Contact, Newsのコンテンツがあり、それぞれAPIを叩いてデータを取得するようにしました。
データ取得部分はベタ書きで仮の値を入れて実装しました。
src以下の構成はこんな感じで簡単にしました。
NewsとWorksは一覧を表示するページと、個々の詳細を表示するページがあります。
src ├── components │ └── WorkCard.tsx ├── layouts │ └── DefaultLayout.tsx ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── about │ │ └── index.tsx │ ├── contact │ │ └── index.tsx │ ├── index.tsx │ ├── news │ │ ├── [slug] │ │ │ └── index.tsx │ │ └── index.tsx │ └── works │ ├── [slug] │ │ └── index.tsx │ └── index.tsx ├── styles │ └── globals.css └── types └── type.ts
Newt側の設定
スペース作成
スペースとは、チームや個人に関わるあらゆるコンテンツを統合的に管理するためのものです。 スペースには、Appと呼ばれるコンテンツ管理のグループをいくつも作成することができ、ワークスペースのメンバーは必要に応じてこれらのAppに参加することでコンテンツ管理作業を行います。
App作成
Appとは、コンテンツやコンテンツの管理者を、その関連度の高さを元にひとまとまりにした「コンテンツ管理のユニット」です。
私はこのような構成にしました 各Appにそれぞれ、モデル(コンテンツ管理の骨組み。データ構造のこと。 (ex) タイトル、スラッグ、メタ情報・・・)、ビュー(管理画面のUI)が存在します
App名 | App UID |
---|---|
works | works |
about | about |
contact | contact |
news | news |
モデルを追加する
モデルとは、コンテンツ管理の骨組みとなるものです。
モデルは、1つ以上のフィールドによって構成されており、それらの組み合わせによってコンテンツのデータ構造や入稿画面のUIが決定されます。 モデル
フィールドを追加していきます。
右上の保存ボタンを忘れないこと!!これを忘れていて、詰まりました。
これはメインコンテンツである、Workのモデルです。
worksでは一覧ページと詳細ページが存在するので、スラッグでidを指定して、個別のページを取得できるようになっています。
カバー画像は一枚で、サブ画像は複数設定できます。
ここでJSONのプレビューも確認できます
{ "_id": "_id", "_sys": { "createdAt": "2022-01-01T00:00:00.000Z", "updatedAt": "2022-01-01T00:00:00.000Z", "raw": { "createdAt": "2022-01-01T00:00:00.000Z", "updatedAt": "2022-01-01T00:00:00.000Z", "firstPublishedAt": "2022-01-01T00:00:00.000Z", "publishedAt": "2022-01-01T00:00:00.000Z" } }, "title": "text", "slug": "text", "meta": { "title": "text", "description": "text", "ogImage": { "_id": "imageId", "src": "imageUrl", "fileType": "image/png", "fileSize": 12345678, "fileName": "image.png", "width": 600, "height": 400 } }, "body": "<p>Plain text is available using the fmt operator.</p>", "coverImage": { "_id": "imageId", "src": "imageUrl", "fileType": "image/png", "fileSize": 12345678, "fileName": "image.png", "width": 600, "height": 400 }, "subImages": [ { "_id": "imageId", "src": "imageUrl", "fileType": "image/png", "fileSize": 12345678, "fileName": "image.png", "width": 600, "height": 400 } ], "member": "<p>Plain text is available using the fmt operator.</p>" }
表示したいデータを登録していく
モデルを登録したら、表示したいデータを登録していきます。 「Work(モデル名)を追加」ボタンをクリックすると、先に設定したフィールド項目がフォームになっているので、埋めていきます。
Newt CDN API Tokenを生成する
グローバルに分散されたCDNを経由してデータを取得します。キャッシュされたリクエストに対して非常に高速なレスポンスを返すことができます。
また、「Newt CDN API」は公開されたコンテンツのみを取得することができ、下書き状態のコンテンツについては取得できません。
スペース設定の「APIキー」画面から作成します
Next.js側の設定
こちらの公式ブログが詳しいです
Vercelでホスティングする
こちらの公式ブログが詳しいです
GitHubのリポジトリとVercelを接続して、ホスティングする
ローカルでは、.env.local
に登録していた環境変数をVercel側に登録するのを忘れていてエラーを出してしまいました、忘れずに。
自動デプロイ設定する
毎回毎回Vercel上で手動デプロイするのは不便なので、Newt側で非エンジニアがコンテンツを更新(公開)したときも自動でデプロイされるようにしたいです。
まず、VercelでProjectのsetting > gitからDeploy Hooksを作成する(hooks名、ブランチ名)。作成したURLはコピーしておく。
続いて、Newt側のスペース設定>Webhook で、VercelのDeploy hooksを登録します。先のURLを貼り付け。
完成!
半日くらいで作成できました!
一度作成してしまえばメンテナンスコスト0で、非エンジニアでも好きなタイミングでコンテンツを更新できるので、作ってよかったです。
また、このHPきっかけで友達の活動が編集者の方の目に止まり建築雑誌に掲載されたりと嬉しい反応もあり、友達も喜んでくれたので嬉しかったです! タテモノトカ
Storybook 6.x から 7.0へアップグレードするときに詰まったこと
概要
StorybookをStorybook 6.x から 7.0へアップグレードしたときに、公式のMigrationガイドを元に進めたものの、テストがFlakyになってしまったり、落ちてしまったり、修正に苦労したのでまとめます。 アップグレード方法はざっとだけ触れます
全体のおおまかな流れ
- Storybook7へアップグレード
- 廃止されたaddonの削除
- main.ts の修正
- 廃止された型の修正
@storybook/testing-react
アンインストール- 詰まったところ
公式のMigration Guide
Migration guide for Storybook 7.0
Storybook7へアップグレード
pnpm dlx storybook@latest upgrade
https://storybook.js.org/docs/react/migration-guide#automatic-upgrade
deprecatedになったaddonの削除 / 新規addon追加
互換性がなくなったaddonがあるとAttentionがでるので削除する
一覧 Community outreach to upgrade addons to 7.0 · Issue #20529 · storybookjs/storybook · GitHub
main.tsの修正
main.jsはこちらを参考に Configure Storybook
Next.jsのroutingなど、addon不要になったのが個人的に嬉しかった🎉
Integrate Next.js and Storybook automatically
廃止された型の修正
storybook/MIGRATION.md at next · storybookjs/storybook · GitHub
ComponentStory, ComponentStoryObj, ComponentStoryFn and ComponentMeta types are deprecated
The type of StoryObj and StoryFn have been changed in 7.0 so that both the "component" as "the props of the component" will be accepted as the generic parameter.
ジェネリックパラメータとして、コンポーネント自体とコンポーネントのプロパティが受け入れられるようになった
import type { Story } from '@storybook/react'; import { Button, ButtonProps } from './Button'; // This works in 7.0, making the ComponentX types redundant. const meta: Meta<typeof Button> = { component: Button }; export const CSF3Story: StoryObj<typeof Button> = { args: { label: 'Label' } }; export const CSF2Story: StoryFn<typeof Button> = (args) => <Button {...args} />; CSF2Story.args = { label: 'Label' }; // Passing props directly still works as well. const meta: Meta<ButtonProps> = { component: Button }; export const CSF3Story: StoryObj<ButtonProps> = { args: { label: 'Label' } }; export const CSF2Story: StoryFn<ButtonProps> = (args) => <Button {...args} />; CSF2Story.args = { label: 'Label' };
@storybook/testing-react
アンインストール
Hey there! I wanted to thank you for using @storybook/testing-react!
@storybook/testing-react has been promoted to a first-class Storybook functionality in Storybook 7. This means that you no longer need this package, and this package will not be worked on anymore (especially regarding Storybook 6, unless there are security issues). Instead, you can import the same utilities, but from the @storybook/react package. Additionally, the internals of composeStories and composeStory have been revamped, so the way a story is composed is way more accurate, and it's possible this issue doesn't happen there anymore.
Please do the following:
Upgrade to Storybook 7 if you haven't already Uninstall @storybook/testing-react Update your imports from @storybook/testing-react to @storybook/react
@storybook/testing-reactは同じユーティリティを@storybook/reactパッケージからインポートできるようになったので、不要になる。アンインストールする。
// Component.test.jsx - import { composeStories } from '@storybook/testing-react'; + import { composeStories } from '@storybook/react'; // setup-files.js - import { setProjectAnnotations } from '@storybook/testing-react'; + import { setProjectAnnotations } from '@storybook/react';
詰まったところ
上記の一通りの更新作業を終えたところ、StoryをJestで再利用しているのですが、Storybookのplay関数を使ってテストしている箇所や、userEventのsetup関数を使っている箇所が尽く落ちるようになってしまった。
コードは公式より引用
export const InputFieldFilled: Story<InputFieldProps> = { play: ({ canvasElement }) => { const canvas = within(canvasElement); userEvent.type(canvas.getByRole('textbox'), 'Hello world!'); }, };
const { InputFieldFilled } = composeStories(stories); test('renders with play function', async () => { const { container } = render(<InputFieldFilled />); // pass container as canvasElement and play an interaction that fills the input await InputFieldFilled.play({ canvasElement: container }); const input = screen.getByRole('textbox') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); });
対処方法
非同期処理に修正
- play: ({ canvasElement }) => { + play: async ({ canvasElement }) => { const canvas = within(canvasElement) - userEvent.type(canvas.getByRole('textbox'), 'Hello world!') + await userEvent.type(canvas.getByRole('textbox'), 'Hello world!') }, }
関連ライブラリを全部最新化 storybookだけアップデートしたものの、testing-library周りを最新化していなかった しばらくupdateしていなかったもの↓ これでplay関数周りは治った
"@testing-library/dom": "9.3.3", "@testing-library/jest-dom": "6.1.3", "@testing-library/user-event": "14.5.1",
testing-library/jest-domのv6 アップデートについて詰まったところ
testing-library/jest-dom をv6系にupdateすると、型を手動で取り込む必要があるので、グローバルな型定義ファイル(global.d.ts
)にマッチャーの型定義を手動でインポートして定義する必要があった。
公式issue
※公式issueではこの問題はv6でresolvedとあるが、できなかった
userEvent.typeで詰まったところ
userEvent.type not workingのissue issue
おそらくこのissueと同じ?でuserEvent.type
を使用している箇所がflakyになってしまった。
↓公式のこの書き方だと、user.typeもoKだったけど、setup関数化して使っているところはflakyになる
Introduction | Testing Library
import userEvent from '@testing-library/user-event' // setup function function setup(jsx) { return { user: userEvent.setup(), // Import `render` from the framework library of your choice. // See https://testing-library.com/docs/dom-testing-library/install#wrappers ...render(jsx), } } test('render with a setup function', async () => { const {user} = setup(<MyComponent />) // ... })
const user = userEvent.setup() // Import `render` and `screen` from the framework library of your choice. // See https://testing-library.com/docs/dom-testing-library/install#wrappers render(<MyComponent />)
zodでうるう年判定(有効日付)のバリデーション
概要
日付を登録するフォームで、有効日付(うるう年など)かどうかを判定したい。 年月日が分かれている選択フォームの場合、日付のバリデーションで迷ったのでメモ
環境
react 18.2.0
date-fns 2.29.3
react-hook-form 7.43.9
zod 3.21.4
簡易なフォームをつくる
年・月・日が分かれているフォーム
import "./styles.css"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { isValid } from "date-fns"; type FormData = { registerYear: string; registerMonth: string; registerDay: string; }; const schema = z .object({ registerYear: z.string().nonempty(), registerMonth: z.string().nonempty(), registerDay: z.string().nonempty() }); export default function App() { const { register, handleSubmit, formState: { errors } } = useForm<FormData>({ resolver: zodResolver(schema) }); const onSubmit = handleSubmit((data) => console.log(data)); return ( <form onSubmit={onSubmit}> <div id="birthday">登録日</div> <div style={{ display: "flex", flexDirection: "column", marginBottom: "10px" }} > <div style={{ marginBottom: "20px" }} > <input id="yearInput" type="number" min="2023" max="2040" aria-labelledby="birthday yearLabel" {...register("registerYear")} /> <label id="yearLabel" htmlFor="yearInput"> 年 </label> <input id="monthInput" type="number" min="1" max="12" aria-labelledby="birthday monthLabel" {...register("registerMonth")} /> <label id="monthLabel" htmlFor="monthInput"> 月 </label> <input id="dayInput" type="number" min="1" max="31" aria-labelledby="birthday dayLabel" {...register("registerDay")} /> <label id="dayLabel" htmlFor="dayInput"> 日 </label> </div> {errors.registerYear && <p>{errors.registerYear.message}</p>} <button id="savebutton" aria-labelledby="birthday savebutton" type="submit" style={{ width: "100px" }} > 保存する </button> </div> </form> ); }
うるう年の判定をしたい
date-fns
はisValid
という一見、有効日付の判定を行えそうなメソッドがあるものの、うるう年、31日が存在しない日付の判定ができない。32日などどんな月でも有効でない日付はfalse
になる。
ここでは、有効日付を確認するために、Dateオブジェクトを生成して確認する。
例:指定された日付が存在しない場合、JavaScriptのDateは以下のようになる
console.log(new Date("2023-09-30")); // Sat Sep 30 2023 09:00:00 GMT+0900 (Japan Standard Time) console.log(new Date("2023-09-31")); // Sun Oct 01 2023 09:00:00 GMT+0900 (Japan Standard Time)
"2023-09-30"は有効な日付なのでそのまま解釈する
"2023-09-31"は存在しない日付だが、自動的に日付を"2023-10-01"に修正する
有効な日付でない場合、Dateオブジェクトが自動的に有効な日付に変換するので、フォームで選択した値でDateオブジェクトを生成して、選択した値と一致するかどうかで有効日付か否かを判定する。
// 追加 const getSelectedDate = ({ registerYear, registerMonth, registerDay }: FormData) => { return new Date( Number(registerYear), Number(registerMonth) - 1, // Dateオブジェクトは月の値が0から始まるので、選択された値から1を引く Number(registerDay) ); }; const schema = z .object({ registerYear: z.string().nonempty(), registerMonth: z.string().nonempty(), registerDay: z.string().nonempty() }) .refine( (data) => { const selectedDate = getSelectedDate(data); return ( isValid(selectedDate) && // 無効な日付かチェック // 選択した年月日がDateオブジェクトによる無効値の自動修正によって変更されていないことを確認 selectedDate.getFullYear() === Number(data.registerYear) && selectedDate.getMonth() + 1 === Number(data.registerMonth) && selectedDate.getDate() === Number(data.registerDay) ); }, { message: "存在しない日付です", path: ["registerYear"] } )
参考にした記事
JavaScript day.js, date-fns で実在する日付かどうか判定したい - かもメモ 【JavaScript】 うるう年判定の一番簡単な方法
WebViewを確認するアプリをつくる
概要
- AndroidでWebViewを利用している箇所のデバッグする方法が全くわからなかったので、調べながらまとめてみました
- Android StudioでWebViewを確認できるだけの簡易的なアプリをつくっていきます
Android Studioをダウンロードする
Download Android Studio & App Tools - Android Developers
時間かかる
Projectをつくる
- New Project
- Empty View Activityを選択する
ファイル作成
- インストールが終わったらファイルを書き換えていく
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <WebView android:id="@+id/web_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
package com.example.myapplication; import android.os.Bundle; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); WebView webView = findViewById(R.id.web_view); webView.setWebViewClient(new WebViewClient()); // JavaScriptを有効に webView.getSettings().setJavaScriptEnabled(true); // デバッグ可能に WebView.setWebContentsDebuggingEnabled(true); webView.loadUrl("https://www.hatenablog.com/"); } }
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.INTERNET" /> // インターネット接続を許可する <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.MyApplication" android:usesCleartextTraffic="true" // 暗号化されていないトラフィックを送信または受信するかどうか。localhostでのデバッグをするので追加 tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
ビルドする
ファイルが準備できたら、ビルドする(ツールバーのBuild-Make Project)
エミュレーターを起動する
Device Manager > 端末を指定して起動、RUNを実行する
WebViewでlocalhostをロードする
url部分を書き換える。
エミュレーター自身が127.0.0.1
を指すため、開発マシンのlocalhostは10.0.2.2
を指定する
Android Emulator のネットワークをセットアップする | Android Studio | Android Developers
// 略 public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { // 略 webView.loadUrl("http://10.0.2.2:3000"); // http://10.0.2.2:<ポート番号>/ } }
完成
参考記事
aria-labelledby属性を試してみる
概要
aria-labelledby
属性が???だったので調べていたところ、そもそもアクセシビリティについて無知だったので、WAIのことから動作確認も含めて試してみた
アクセシビリティとは?
What is accessibility? - Learn web development | MDN
Accessibility is the practice of making your websites usable by as many people as possible. We traditionally think of this as being about people with disabilities, but the practice of making sites accessible also benefits other groups such as those using mobile devices, or those with slow network connections.
できるだけ多くの人に対し、Webサイトを利用しやすくすること。障がいのある方のためだけではなく、モバイルデバイス利用者やネット速度が遅いユーザに対しても有益である。
WAI
Web Accessibility Initiative(WAI) アクセシビリティへの理解向上と基準、サポート資料を率先して行っている組織。 W3Cの内部組織として発足した。 Home | Web Accessibility Initiative (WAI) | W3C
W3Cとは(The World Wide Consortium)Web: HTML,CSS and more の国際標準化を推進している組織。
アクセシビリティガイドライン(WCAG)
WAIが策定しているアクセシビリティを推進するためのガイドライン WCAG 2 Overview | Web Accessibility Initiative (WAI) | W3C
WAI-ARIA (Accessible Rich Internet Applications)
WAI-ARIA は W3C によって定められた仕様で、要素に適用できる追加の意味論を提供する一連の HTML 属性を定義しており、それが欠けているどのような場所でもアクセシビリティを向上させます。
WAI-ARIA Overview | Web Accessibility Initiative (WAI) | W3C WAI-ARIAの基本 - ウェブ開発を学ぶ | MDN
スクリーンリーダーを試してみる
macOSの場合はcommand + touchID3回クリック
でVoid Overが使用できる。
後は読み上げたいWebサイトに行くだけでスクリーンリーダーが起動する。
aria-*属性を試してみる
よく使う属性2つをスクリーンリーダーで試しながら使ってみる
aria-label属性
アイコンボタンなど、視覚的には意味がわかるものの、テキストを持たない要素にラベルを与えるために使用する。
例えば、X
を閉じるボタンとして使用している例。
これをスクリーンリーダーで読み上げると、「閉じる ボタン」と読み上げてくれる。元のラベルである「X」は上書きされる。
<button aria-label="閉じる">X</button>
aria-labelledby属性
aria-label
と同じ動きをするが、属性値には文字列ではなく、要素のIDを指定する。- スペースで区切って、IDを複数指定することもできる。
生年月日を送信するフォームで、年・月・日が複数の入力フィールドに分かれている場合、「生年月日」という一つの情報を取り扱っているとaria-labelledby
属性を使って表現する例
<div id="birthday">生年月日</div> <input id="yearInput" type="number" aria-labelledby="birthday yearLabel" // 生年月日のラベルIDをつけている /> <label id="yearLabel" htmlFor="yearInput"> 年 </label> <input id="monthInput" type="number" aria-labelledby="birthday monthLabel" /> <label id="monthLabel" htmlFor="monthInput"> 月 </label> <input id="dayInput" type="number" aria-labelledby="birthday dayLabel" /> <label id="dayLabel" htmlFor="dayInput"> 日 </label> <button id="savebutton" aria-labelledby="birthday savebutton"> 保存する </button>
こうすると、birthday
と関連づけられるので、スクリーンリーダーでは、「生年月日 年」「生年月日 月」「生年月日 日」と読み上げてくれる
【React】スクロールダウン・アップでボタンの非表示・表示を切り替える
概要
スクロールアップ時に表示され、スクロールダウン時に非表示になるButtonコンポーネントをつくりたい
要件・流れ
- スクロールアップ ---ボタン表示
スクロールダウン ---ボタン非表示
以前のスクロール位置を
useState
で保持する- ボタンの非表示・表示は
display: block
display: none
を切り替えて管理する - 前回のスクロール位置が現在のスクロール位置よりも大きい場合、ボタンを表示する。これは、ユーザーがページを上にスクロールしていることと同意
export const ScrollButton = () => { // ボタン表示・非表示の状態 const [isVisible, setIsVisible] = useState(true); // 前回のスクロール位置 const [prevScrollPosition, setPrevScrollPosition] = useState(0); const handleScroll = () => { // 現在のスクロール位置を取得 const currentScrollPos = window.scrollY; // 前回のスクロール位置が現在のスクロール位置よりも大きい場合、ボタンを表示する const isVisible = prevScrollPosition > currentScrollPos; setIsVisible(isVisible); setPrevScrollPosition(currentScrollPos); }; useEffect(() => { window.addEventListener("scroll", handleScroll); // クリーンアップ return () => { window.removeEventListener("scroll", handleScroll); }; }, [handleScroll]); return ( <button style={{ display: isVisible ? "block" : "none" }}> Float Button </button> ); };
Refactor
現在だとスクロールイベントが高頻度で発生してしまうため、パフォーマンスに影響を与えてしまう可能性がある。
そのため、debounce
関数を使って、イベントを間引きして実行するように修正していく。
import React, { useState, useEffect } from "react"; import { debounce } from "lodash"; export const ScrollButton = () => { const [isVisible, setIsVisible] = useState(true); const [prevScrollPosition, setPrevScrollPosition] = useState(0); const handleScroll = () => { const currentScrollPos = window.scrollY; const isVisible = prevScrollPosition > currentScrollPos; setIsVisible(isVisible); setPrevScrollPosition(currentScrollPos); console.log("scroll"); }; const debounceHandleScroll = debounce(handleScroll, 100); useEffect(() => { window.addEventListener("scroll", debounceHandleScroll); return () => { window.removeEventListener("scroll", debounceHandleScroll); }; }, [debounceHandleScroll]); return ( <button style={{ display: isVisible ? "block" : "none" }}> Float Button </button> ); };
bounce関数使用前
bounce関数使用後
参考にしたサイト
MySQLでReadOnly権限のユーザーを作成する
概要
BIツール上でデータの更新・削除が行われることを防ぐために、MySQLでREADONLY権限のUserを作成したい。
前提
AWS踏み台サーバ経由でRDSに接続している
流れ
## EC2インスタンスへsshログインする ~/.ssh ❯ ssh -i ~/.ssh/id_rsa(秘密鍵) ec2-user@xxxxxxx
## MySQLへログインする [ec2-user@xxxxxxx]$ mysql -h RDSエンドポイント -p -u dbuser(ユーザー名) Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is xxxxx Server version: 5.7.33-log Source distribution Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
### SELECT権限を持つユーザー作成 mysql> GRANT SELECT ON *.* TO (新しく作るユーザー名)@'%' IDENTIFIED BY '(パスワード)'; mysql> GRANT SELECT ON *.* TO read-user@'%' IDENTIFIED BY 'passward';
## ユーザ情報の反映 mysql> FLUSH PRIVILEGES;
確認コマンド
## ユーザーの権限情報が確認できる mysql> SHOW GRANTS FOR 'ユーザー名'@'%';
## 現状のユーザー情報確認 mysql> select user, host from mysql.user; +---------------+-----------+ | user | host | +---------------+-----------+ | user | % | | mysql.session | localhost | | mysql.sys | localhost | +---------------+-----------+