Skip to main content
Version: 5.3

Accessing currentUser in the API side

私たちのブログが数百万ドル規模の企業に発展したため、私たちはお金を数えるのに忙しく、実際のブログ記事を書く時間がもはやないことに気づきました!ブログ記事を書いてもらうためにライターを雇うことにしましょう。

複数のユーザが記事を作成できるようにするには、何を変更する必要があるのでしょうか?まず、ブログ記事を、それを書いた著者に関連付け、その人の名前を表示できるようにします。また、ブログ記事を読む画面に著者の名前を表示するようにします。最後に、著者が編集できるブログ記事のリストを自分のものだけに制限したいと思います:アリスはボブの記事に変更を加えることができないようにします。

Associating a Post to a User

ここでは、 PostUser の間のリレーションシップ(外部キー)を紹介します。これは一対多のリレーション(一人の User が多くの Post を持つ)で、先ほど作成した Post とそれに関連する Comment のリレーションと同じようなものと考えてください。新しいスキーマはこのようになります:

┌─────────────────────┐       ┌───────────┐
│ User │ │ Post │
├─────────────────────┤ ├───────────┤
│ id │───┐ │ id │
│ name │ │ │ title │
│ email │ │ │ body │
│ hashedPassword │ └──<│ userId │
│ ... │ │ createdAt │
└─────────────────────┘ └───────────┘

このようなデータの変更は、すぐに自然にできるようになります:

  1. 新しいリレーションシップを schema.prisma ファイルに追加
  2. データベースをマイグレーション
  3. SDLとサービスを生成/更新

Add the New Relationship to the Schema

まず、新しいフィールド userIdPost に追加し、リレーションを User に追加します:

api/db/schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
body String
comments Comment[]
user User @relation(fields: [userId], references: [id])
userId Int
createdAt DateTime @default(now())
}

model User {
id Int @id @default(autoincrement())
name String?
email String @unique
hashedPassword String
salt String
resetToken String?
resetTokenExpiresAt DateTime?
roles String @default("moderator")
posts Post[]
}
User SDL

第4章でブログの認証を設定したときにUserモデルを作成しました。Redwoodの setup auth dbAuth コマンドは認証を管理する2つのファイルを生成してくれました: api/src/lib/auth ファイルと、 api/src/functions/auth ファイルです。どちらのファイルも PrismaClient を直接使用して User モデルと連携するため、User モデル用の SDL やサービスをセットアップする必要はありませんでした。

Intermissionでの勧めに従って example リポジトリを使用した場合は User SDL とサービスがすでに追加されています。そうでない場合は自分で追加する必要があります:

yarn rw g sdl User --no-crud

GraphQL の User type の機密情報属性をコメントアウトして、漏洩する可能性をなくしておきます:

api/src/graphql/users.sdl.js
  type User {
...
# hashedPassword: String!
# salt: String!
# resetToken: String
# resetTokenExpiresAt: DateTime
}
api/src/graphql/users.sdl.tsx
  type User {
...
# hashedPassword: String!
# salt: String!
# resetToken: String
# resetTokenExpiresAt: DateTime
}

Migrate the Database

次に、変更を適用するためにデータベースをマイグレーションします(オプションがある場合は、マイグレーションに "add userId to post" のような名前を付けてください):

yarn rw prisma migrate dev

Whoops!

image

Userroles を追加したときと同じように、 userId を必須フィールドにしましたが、開発用データベースにはすでにいくつかのブログ記事があります。userId のデフォルト値が定義されていないため、このカラムをデータベースに追加できません。

Why don't we just set @default(1) in the schema?

これはこの問題を解決してくれますが、将来的に追跡が困難なバグを引き起こす可能性があります:もし postuser に割り当てるのを忘れた場合、失敗するどころか喜んで userId1 に設定してしまうでしょう!このような問題を解決するためには、時間をかけて正しい方法で行い、手っ取り早いハックは避けたほうがよいでしょう。未来のあなたは、今のあなたに感謝することでしょう!

開発中なので、データベースは吹っ飛ばしてやり直しましょう:

yarn rw prisma migrate reset
Database Seeds

Redwood Tutorial repo から後半のチュートリアルを開始した場合、データベースをリセットした後にエラーが発生します -- Prismaがデータベースにユーザとブログ記事をシードしようとしましたが、シードのブログ記事には新しい必須フィールドである userId がありません! scripts/seed.js を開き、各ブログ記事をに userId: 1 を追加してください:

scripts/seed.js
{
id: 1,
name: 'John Doe',
title: 'Welcome to the blog!',
body:
"I'm baby single- origin coffee kickstarter lo - fi paleo skateboard.Tumblr hashtag austin whatever DIY plaid knausgaard fanny pack messenger bag blog next level woke.Ethical bitters fixie freegan,helvetica pitchfork 90's tbh chillwave mustache godard subway tile ramps art party. Hammock sustainable twee yr bushwick disrupt unicorn, before they sold out direct trade chicharrones etsy polaroid hoodie. Gentrify offal hoodie fingerstache.",
userId: 1,
},

ここで yarn rw prisma migrate reset を実行すると...違うエラーが表示されます。でも大丈夫です、続きを読んでください...

データベースの reset を実行しても、保留中のマイグレーションが適用されないので、ここでエラーが発生しました。 つまり、データベースに存在しない userId を設定しようとしています(Prisma が生成したクライアントライブラリには存在するので、データベースにまだ存在しなくても、存在 するはず だと考えているのです)。

行き詰まったように感じるかもしれませんが、データベースは正常にリセットされ、シードが失敗しただけであることに注意してください。 そこで、新しい userIdPost に追加するためにデータベースにマイグレーションを実行し、データベースにデータを投入するためにシードを再実行し、 "add userId to post" のような名前をつけます:

yarn rw prisma migrate dev

そしてシードを実行します:

yarn rw prisma db seed
info

Redwood Tutorial repo からコードベースを立ち上げなかった場合、データベースにはユーザもブログ記事も1つもありません。 http://localhost:8910/signup にアクセスしてユーザを作成します。ただし、まだブログ記事は作成しないでください! ユーザのロールを "admin" に変更します。previous page で紹介したコンソールを使用するか、 Prisma Studio を開いて直接データベースで変更することができます。

Add Fields to the SDL and Service

新しいリレーションシップをどこに表示するか考えてみましょう。今のところ、ホームページとブログ記事ページだけでしょう:では、ブログ記事の著者をタイトルの横に表示しましょう。 つまり、次のようなGraphQLクエリで、ブログ記事からユーザにアクセスしたいのです:

post {
id
title
body
createdAt
user {
name
}
}

これを可能にするためには、APIサイドで2つの修正を行う必要があります:

  1. posts SDL に user フィールドを追加
  2. posts サービスの userrelation resolver を追加

Add User to Posts SDL

api/src/graphql/posts.sdl.js
  type Post {
id: Int!
title: String!
body: String!
createdAt: DateTime!
user: User!
}
What about the mutations?

CreatePostInputUpdatePostInput 型に useruserId を追加することはして いません 。新しく作成されたブログ記事にユーザを設定したいのですが、GraphQL 呼び出しによって誰でもそれを実行できるようにしたくはないのです!GraphQLペイロードを変更するだけで、簡単にブログ記事を作成または編集して、他の誰かに割り当てることができます。ユーザの割り当てをサービスだけに保存して、外部からは操作できないようにします。

ここでは感嘆符付きの User! を使っていますが、これはすべての Post がそれに関連するユーザを持つことが分かっているからです -- このフィールドは決して null にはなりません。

Add User Relation Resolver

これは少しトリッキーです: posts サービスに "lookup" を追加して、関連するユーザを取得する方法を知る必要があります。comments SDL とサービスを生成したときに、この relation resolver が作成されました。Post サービス用のサービスジェネレータを再実行することもできますが、 このファイルに加えた変更が吹っ飛ぶ可能性があります。唯一の選択肢は、ファイルがすでに存在しているので、 --force フラグを含めることです。これで全て上書きされます。この場合、手動でリゾルバを追加することになります:

api/src/services/posts/posts.js
import { db } from 'src/lib/db'

export const posts = () => {
return db.post.findMany()
}

export const post = ({ id }) => {
return db.post.findUnique({
where: { id },
})
}

export const createPost = ({ input }) => {
return db.post.create({
data: input,
})
}

export const updatePost = ({ id, input }) => {
return db.post.update({
data: input,
where: { id },
})
}

export const deletePost = ({ id }) => {
return db.post.delete({
where: { id },
})
}

export const Post = {
user: (_obj, { root }) =>
db.post.findFirst({ where: { id: root.id } }).user(),
}

これは直感的でない場合があるので、順を追って説明します。まず、このサービスが対象とするモデルと同じ名前の変数を宣言します: posts サービスでは Post です。そうしたら、この変数には、検索対象となるフィールドと同じキーを持つオブジェクトを設定します。この場合は user です。GraphQLがこの関数を呼び出すとき、いくつかの引数を渡します。そのうちのひとつが root で、これは最初に解決されたオブジェクト、この場合はGraphQLクエリの post に渡されます:

post {   <- root
id
title
body
createdAt
user {
name
}
}

ブログ記事はすでにデータベースから取得されているので、その id はわかっています。 root はそのオブジェクトなので、単純に .id を呼び出してそのプロパティを取得できます。 これで、Prismaで findFirst() クエリを作成するために必要なことが全てわかりました。すでに見つけたレコードの id を指定して、 post 自体ではなく、そのレコードに関連する user を返します。

リゾルバは次のようにも書くことができます:

export const Post = {
user: (_obj, { root }) =>
db.user.findFirst({ where: { id: root.userId } }),
}

上記のリレーションリゾルバを使いながら postspost から返されるブログ記事に user プロパティを含める場合、このフィールドリゾルバは依然として呼び出され、返されるものはすでに存在する user プロパティをオーバーライドすることに注意しましょう。なぜでしょうか?それは、GraphQLがどのように動作するかということです -- リゾルバが存在する場合、たとえ root にすでにそのデータが含まれていても、常にリゾルバが呼び出され、その戻り値が使用されます。

Prisma and the N+1 Problem

もしあなたがデータベースの設計や検索の経験があるなら、この方法が理想的な解決策ではないことにお気づきかもしれません:ブログ記事が見つかるたびに、その post に関連するユーザデータを取得するために 追加 のクエリを実行する必要があり、これは N+1 problem とも呼ばれます。これはGraphQLクエリの性質によるものです:各リゾルバ関数は自分の親オブジェクトについてだけ知っていて、潜在的な子オブジェクトについては何も知らないのです。

この問題を回避するために、いくつかの試みがなされてきました。余分な依存関係がないシンプルなものは、このフィールドリゾルバを削除して、データベースから取得した post と一緒に user データを含めるだけです:

export const post = ({ id }) => {
return db.post.findUnique({
where: { id },
include: {
user: true
}
})
}

これはあなたの役に立つかもしれないし、役に立たないかもしれません:GraphQLクエリで要求されていなくても常にユーザデータを返すというオーバーヘッドが発生します。 さらに、これはクエリのネスティングを壊すことになります:このブログ記事のユーザと、そのユーザが作成した他のすべてのブログ記事IDのリストを返したいとしたらどうでしょうか?

post {
id
title
body
createdAt
user {
name
posts {
id
}
}
}

このクエリは失敗します。 post.user しか利用できず、post.user.posts は利用できないためです。

Redwoodチームは、N+1問題に対するよりエレガントな解決策を積極的に検討していますので、ご期待ください!

Displaying the Author

著者情報を取得するためには、セルのクエリを更新して、ユーザ名を取得しなければなりません。

ブログ記事を公開する場は2つあります:

  1. ホームページ
  2. ブログ記事の個別ページ

それぞれのセルを更新して、ブログ記事を作成したユーザの名前を含めるようにしましょう:

web/src/components/ArticlesCell/ArticlesCell.js
export const QUERY = gql`
query ArticlesQuery {
articles: posts {
id
title
body
createdAt
user {
name
}
}
}
`
web/src/components/ArticleCell/ArticleCell.js
export const QUERY = gql`
query ArticleQuery($id: Int!) {
article: post(id: $id) {
id
title
body
createdAt
user {
name
}
}
}
`

そして、Articleを表示する表示コンポーネントを更新します:

web/src/components/Article/Article.js
import { Link, routes } from '@redwoodjs/router'

const Article = ({ article }) => {
return (
<article>
<header>
<h2 className="text-xl text-blue-700 font-semibold">
<Link to={routes.article({ id: article.id })}>{article.title}</Link>
<span className="ml-2 text-gray-400 font-normal">
by {article.user.name}
</span>
</h2>
</header>

<div className="mt-2 text-gray-900 font-light">{article.body}</div>
</article>
)
}

export default Article

Redwood Tutorialのリポジトリから始めたかどうかによって、実際に表示するブログ記事がない場合があります。では、ブログ記事を追加してみましょう!しかし、ブログ記事管理画面あるいはscaffoldでそれを行う前に、実際にユーザをそのユーザが作成したブログ記事に関連付ける必要があります。GraphQL経由で userId を設定することはできません。これはscaffoldがレコードを作成/編集するときに使用するものです。しかし大丈夫です。これはいずれにせよ、サービスの中だけで行われるようにしたいのです。

Accessing currentUser on the API side

サービス関数の中では context という不思議な変数が使えます。この変数には、サービス関数が呼び出されているときのコンテキストが格納されています。このコンテキストで使用できるプロパティのひとつは、ログインしているユーザです(誰かがログインしている のであれば )。これは、Webサイドで利用可能な currentUser と同じです:

api/src/service/posts/posts.js
export const createPost = ({ input }) => {
return db.post.create({
data: { ...input, userId: context.currentUser.id }
})
}

そのため、このリクエストを行ったユーザにアクセスする必要がある場合は、常に context.currentUser が存在することになります。このユーザの id を取得し、新しいブログ記事を作成するときに scaffold フォームから入力される残りのデータを適用します。やってみましょう!

これでブログ記事管理画面からブログ記事を作成できるはずです:

image

そしてホームページに戻ると、実際にブログ記事とその著者が表示されるようになるはずです!

image

Only Show a User Their Posts in Admin

今のところ /admin/posts にアクセスした管理者は、自分のブログ記事だけでなく、すべてのブログ記事を見ることができます。これを変更しましょう。

このように context.currentUser にアクセスできることがわかったので、これをpostsサービス全体に散りばめ、現在ログインしているユーザが所有しているブログ記事だけに返すものを限定することができます:

api/src/services/posts/posts.js
import { db } from 'src/lib/db'

export const posts = () => {
return db.post.findMany({ where: { userId: context.currentUser.id } })
}

export const post = ({ id }) => {
return db.post.findFirst({
where: { id, userId: context.currentUser.id },
})
}

export const createPost = ({ input }) => {
return db.post.create({
data: { ...input, userId: context.currentUser.id },
})
}

export const updatePost = ({ id, input }) => {
return db.post.update({
data: input,
where: { id },
})
}

export const deletePost = ({ id }) => {
return db.post.delete({
where: { id },
})
}

export const Post = {
user: (_obj, { root }) =>
db.post.findFirst({ where: { id: root.id } }).user(),
}
Prisma's findUnique() vs. findFirst()

ここで findUnique() から findFirst() に変更したことに注意してください。Prisma の findUnique() は、 where 節に含まれるすべての属性がユニークインデックスを持つことが前提で、 id はこの前提を満たしていますが、 userId は満たしていません。そのため findFirst() 関数に変更する必要があります。この関数では、 where に好きなものを入れて、複数のレコードを返すことができますが、Prisma はそのセットの最初のものだけを返します。この場合、 userId に加えて id で選択しているので、常に1つしかないことが分かっています。

この変更により、ユーザは自分のブログ記事の一覧、または自分が書いたブログ記事の詳細のみを見ることができるようになります。

updatePostdeletePost についてはどうでしょうか? これらは currentUser だけには制限されていないので、手動で GraphQL を呼び出せば誰でもブログ記事を更新したり削除したりできることになります!それはまずいですね。それらは 少し後で 対処することにしましょう。

しかし、先程のアップデートで問題が発生しました:ホームページは posts サービスを使用して、ホームページのすべてのブログ記事を表示するのではないでしょうか?このコードの更新によって、ホームページはログインしているユーザのブログ記事だけを表示するように制限され、他のユーザのブログ記事は表示されなくなります!そして、もしログインしていない人がホームページにアクセスしたらどうなるのでしょうか? ERROR

ブログ記事管理画面ではあるブログ記事のリストを返し、ホームページでは別のブログ記事のリストを返すにはどうしたらよいでしょうか?

An AdminPosts Service

GraphQL クエリに変数を追加して、既存の posts サービスでチェックし、ホームページとブログ記事管理画面のどちらにいるかで異なるブログ記事のリストを返すようにすることもできます。 しかし、この複雑さは、テストしなければならない多くの観点を追加し、将来誰かがそこに入ってきたときに壊れやすくなります -- 新しい条件を追加したり、既存の条件を無効にしたりして、誤ってブログ記事管理機能を悪用されないようにすごく注意しなければなりません。

もしブログ記事管理画面用に 新しい GraphQL クエリを作成したらどうでしょうか? これらは @requireAdmin のおかげで自動的にセキュリティチェックが行われ、カスタムコードは必要ありません。 これらの新しいクエリはブログ記事管理画面で使われ、元々のシンプルな posts サービスはホームページとブログ記事詳細ページで使用されるでしょう。

いくつかのステップを経て、完成させる必要があります:

  1. 型を定義した adminPosts SDL を新規作成
  2. 新しい adminPosts サービスを作成
  3. ブログ記事管理画面の GraphQL クエリを更新し、posts ではなく adminPosts から取得

Create the adminPosts SDL

既存の posts.sdl.js を残し、それを "public" インターフェースとします。その SDL を複製し、名前を adminPosts.sdl.js とし、以下のように修正します:

api/src/graphql/adminPosts.sdl.js
export const schema = gql`
type Query {
adminPosts: [Post!]! @requireAuth(roles: ["admin"])
adminPost(id: Int!): Post @requireAuth(roles: ["admin"])
}

input CreatePostInput {
title: String!
body: String!
}

input UpdatePostInput {
title: String
body: String
}

type Mutation {
createPost(input: CreatePostInput!): Post! @requireAuth(roles: ["admin"])
updatePost(id: Int!, input: UpdatePostInput!): Post! @requireAuth(roles: ["admin"])
deletePost(id: Int!): Post! @requireAuth(roles: ["admin"])
}
`
api/src/graphql/posts.sdl.js
export const schema = gql`
type Post {
id: Int!
title: String!
body: String!
createdAt: DateTime!
user: User!
}

type Query {
posts: [Post!]! @skipAuth
post(id: Int!): Post @skipAuth
}
`

そのため、Post という単一の型を維持します。なぜなら、その中に含まれるデータは同じであり、どちらの SDL ファイルもこの同じデータ型を返すからです。 一般ユーザはミューテーションにアクセスする必要がないため、posts SDL からミューテーションを削除することができます。 ミューテーションの作成、更新、削除を新しい adminPosts SDL に移動し、2つのクエリの名前を posts から adminPosts に、post から adminPost に変えました。 ご存知でない方のために説明しますと :すべてのクエリ/ミューテーションは、アプリケーション全体で一意な名前でなければなりません!

adminPosts では、クエリを更新して @skipAuth ではなく @requireAuth を使用するようにしました。これでブログ記事管理画面専用のクエリができたので、認証されたときだけアクセスを許可するように鍵をかけることができます。

Create the adminPosts Service

次に adminPosts サービスを作成します。SDL の名前をサービスの名前と一致させる必要があるので、作成/更新/削除のミューテーションをそこに移動させる必要があります:

api/src/services/adminPosts/adminPosts.js
import { db } from 'src/lib/db'

export const adminPosts = () => {
return db.post.findMany({ where: { userId: context.currentUser.id } })
}

export const adminPost = ({ id }) => {
return db.post.findFirst({
where: { id, userId: context.currentUser.id },
})
}

export const createPost = ({ input }) => {
return db.post.create({
data: { ...input, userId: context.currentUser.id },
})
}

export const updatePost = ({ id, input }) => {
return db.post.update({
data: input,
where: { id },
})
}

export const deletePost = ({ id }) => {
return db.post.delete({
where: { id },
})
}

(繰り返しますが、 findUnique() から findFirst() への変更も忘れないでください。)そして、 posts を更新して、現在 adminPosts にある関数のいくつかを削除します:

api/src/services/posts/posts.js
import { db } from 'src/lib/db'

export const posts = () => {
return db.post.findMany()
}

export const post = ({ id }) => {
return db.post.findUnique({ where: { id } })
}

export const Post = {
user: (_obj, { root }) =>
db.post.findFirst({ where: { id: root.id } }).user(),
}

posts サービスの userId 検索を削除したので、すべてのブログ記事( posts ) または単一のブログ記事( post では誰が所有しているかに関係なく)を返すようになりました。

リレーションリゾルバは Post.user にあり、 adminPosts にはないことに注意しましょう:どちらの SDL もクエリとミューテーションは Post を返すので、リレーションリゾルバは元の SDL と名前が一致するサービスに残しておくことになるでしょう: graphql/posts.sdl.js => services/posts/posts.js

Update the GraphQL Queries

最後に、新しい adminPostsadminPost クエリを使用するために、いくつかの scaffold コンポーネントを更新する必要があります(スペースを節約するために、以下のコードスニペットは変更点だけです。このページも十分長くなってきました!)。

web/src/components/Post/EditPostCell/EditPostCell.js
export const QUERY = gql`
query FindPostById($id: Int!) {
post: adminPost(id: $id) {
id
title
body
createdAt
}
}
`
web/src/components/Post/PostCell/PostCell.js
export const QUERY = gql`
query FindPostById($id: Int!) {
post: adminPost(id: $id) {
id
title
body
createdAt
}
}
`
web/src/components/Post/PostsCell/PostsCell.js
export const QUERY = gql`
query POSTS {
posts: adminPosts {
id
title
body
createdAt
}
}
`

もし、 posts: adminPosts という構文を使わなかった場合は、以下の Success コンポーネントに来る引数を adminPosts にリネームする必要があるでしょう。この構文では、クエリの結果を posts にリネームします!

"public" ビュー( ArticleCellArticlesCell など)は何も変更しなくてよいです。元々の postspost クエリ、およびそれぞれのリゾルバを引き続き使います。

Update and Delete

よし。 updatePostdeletePost をに目を向けましょう。なぜこうすることができなかったのでしょうか?

export const updatePost = ({ id, input }) => {
return db.post.update({
data: input,
where: { id, userId: context.currentUser.id },
})
}

なぜなら、 findUnique() と同様に、Prisma はユニークインデックスを持つフィールドに基づいてレコードを更新したいのであって、この場合は id だけです。そのため、ここは id だけにしておく必要があります。しかし、ユーザが自分が所有するレコードだけを更新/削除していることをどのように確認すればよいでしょうか?

まずレコードを select して、ユーザがそのレコードを所有していることを確認し、それから update() をすることができます:

import { ForbiddenError } from '@redwoodjs/graphql-server'

export const updatePost = async ({ id, input }) => {
if (await adminPost({ id })) {
return true
} else {
throw new ForbiddenError("You don't have access to this post")
}

return db.post.update({
data: input,
where: { id },
})
}

データベースへの別の呼び出しを行うのではなく、adminPost()サービス関数を使用しています(続ける前にポストがあることを確認するために async/await しなければならなかったことに注意してください)。このようなサービスの組み合わせは、Redwoodでは奨励するように設計されています:サービスの関数はGraphQLのリゾルバとして機能しますが、単なるJSの関数でもあり、必要な場所で呼び出すことができます。そして、なぜこのようなことをしたいのか、その理由がここで明確に示されています:adminPost() はすでに、見つかったレコードをログインしているユーザが所有するものだけに制限しているので、そのロジックはすでにここにカプセル化されており、管理者が単一のブログ記事で何かをしたいときはいつでも、このコードを実行し、毎回同じロジックを使用することを確認することができます。

これは動作しますが、同じことを deletePost で行う必要があります。ブログ記事が存在するかどうかのチェックを関数に抜き出してみましょう:

const verifyOwnership = async (id) {
if (await adminPost({ id })) {
return true
} else {
throw new ForbiddenError("You don't have access to this post")
}
}

export const updatePost = async ({ id, input }) => {
await verifyOwnership(id)

return db.post.update({
data: input,
where: { id },
})
}

簡単ですね!最終的な adminPosts サービスは次のようになります:

import { ForbiddenError } from '@redwoodjs/graphql-server'

import { db } from 'src/lib/db'

const validateOwnership = async ({ id }) => {
if (await adminPost({ id })) {
return true
} else {
throw new ForbiddenError("You don't have access to this post")
}
}

export const adminPosts = () => {
return db.post.findMany({ where: { userId: context.currentUser.id } })
}

export const adminPost = ({ id }) => {
return db.post.findFirst({
where: { id, userId: context.currentUser.id },
})
}

export const createPost = ({ input }) => {
return db.post.create({
data: { ...input, userId: context.currentUser.id },
})
}

export const updatePost = async ({ id, input }) => {
await validateOwnership({ id })

return db.post.update({
data: input,
where: { id },
})
}

export const deletePost = async ({ id }) => {
await validateOwnership({ id })

return db.post.delete({
where: { id },
})
}

Wrapping Up

ふぅ〜!いくつかの異なるシナリオを試し、すべてが期待通りに動作していることを確認しましょう(このようなことのためにQAチームは生きているのです):

  • ログアウトしたユーザは、ホームページのすべてのブログ記事を見ることができるはず
  • ログアウトしたユーザは、1つのブログ記事の詳細を見ることができるはず
  • ログアウトしたユーザは、/admin/postsに行くことができ ない はず
  • ログアウトしたユーザは、コメントの横にあるモデレーションコントロールを見ることができ ない はず
  • ログインした管理者ユーザは、ホームページのすべての記事を見ることができるはず(自分の記事だけではなく)
  • ログインした管理者ユーザは、/admin/postsに行くことができるはず
  • ログインした管理者ユーザは、新しいブログ記事を作成することができるはず
  • ログインした管理者ユーザは、/admin/posts にある他の人のブログ記事を見ることができ ない はず
  • ログインした管理者ユーザは、コメントの横にあるモデレーションコントロールを見ることができ ない はず(最後のページでその動作を変更した場合を除く)
  • ログインしたモデレータユーザは、コメントの横にモデレーションコントロールが表示されるはず
  • ログインしたモデレータユーザは /admin/posts にアクセスでき ない べき

実際のところ、この機能が将来誤って変更されないようにするために、新しいテストを書くことができます。一番手っ取り早いのは、新しいサービスに対応するために adminPosts.scenarios.jsadminPosts.test.js を作成し、指定したユーザが所有するブログ記事のみを返すことを確認することでしょうか。currentUserのモック を使って、誰かがログインしているかどうか、異なるロールでシミュレートすることができます。上で修正したセルのテストを追加することもできますが、これらのテストが取得するデータはサービスから返されるものに依存しているので、サービス自体をカバーしていれば問題ありません。100%カバレッジの人たちはそうではないと主張するでしょうが、彼らがまだテストを書くのに忙しい間、私たちは新しく立ち上げた(妥当な テストカバレッジの)機能による収益のおかげで、新しいヨットでクルージングに出かけているのです!

うまくいきましたか?素晴らしい!何か問題があったのでしょうか?誰かが見過ぎたり、見落としたりしませんか?GraphQLクエリがすべて更新され、開いているすべてのファイルに変更が保存されていることを再確認してください。