t3-stack入門 (6)TodoにOwnerを追加する(Server側)

TodoにOwnerを追加して
* 自分のTodoだけ更新・削除削除可能
* ログイン状態の時だけ、Todo追加可能
* 未ログインの場合は、閲覧だけ可能

BtoBのシステムではよくある仕様で、このぐらいやらないと開発の実際の体験はわからない気がします。

## TodoにOwnerプロパティを追加
```diff
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -56,6 +56,7 @@ model User {
   image         String?
   accounts      Account[]
   sessions      Session[]
+  todos         Todo[]
 }
 
 model VerificationToken {
@@ -70,6 +71,8 @@ model Todo {
   id            Int    @id @default(autoincrement())
   title         String
   description   String
+  ownerId       String
+  owner         User     @relation(fields: [ownerId], references: [id])
   createdAt DateTime @default(now())
   updatedAt DateTime @updatedAt
 }
```

prismaでMigrateとgenerateしときます。
```
~/t3-app-example$ npx prisma migrate dev
~/t3-app-example$ npx prisma generate
```


## Trpc用の入力スキーマを追加

```diff_typescript
@@ -6,3 +6,11 @@ export const createTodoSchema = z.object({
     .string()
     .min(5, { message: "Must be 5 or more characters long" }),
 });
+
+export const updateTodoSchema = createTodoSchema.extend({
+  id: z.number(),
+});
+
+export const deleteTodoSchema = z.object({
+  id: z.number(),
+});
```
createだけのSchemaしかなかったので、delete,update用のSchemaを追加。

## Trpc用のコードを追加

### create 
```typescript
create: protectedProcedure
    .input(createTodoSchema)
    .mutation(({ input, ctx }) => {
      return ctx.prisma.todo.create({
        data: {
          title: input?.title,
          description: input?.description,
          ownerId: ctx.session.user.id,
        },
      });
    }),
```
ownerIdにsessionから取得したuserのidを設定します。

### update
```typescript
  update: protectedProcedure
    .input(updateTodoSchema)
    .mutation(async ({ input, ctx }) => {
      const prev = await ctx.prisma.todo.findUniqueOrThrow({
        where: { id: input.id },
      });
      if (prev.ownerId !== ctx.session.user.id) {
        throw new Error("Update Error");
      }
      const newTodo = await ctx.prisma.todo.update({
        where: { id: input.id },
        data: {
          title: input.title,
          description: input.description,
        },
      });
      return newTodo;
    }),
```
ownerIdがsessionのユーザのIdと一致しなかったらErrorを投げるようにしました。
trpcで異常が発生したときどうするのがベストかよくわかっていません。

### getAll
```typescript
  getAll: publicProcedure.query(async ({ ctx }) => {
    return ctx.prisma.todo.findMany({
      select: {
        id: true,
        title: true,
        description: true,
        owner: {
          select: {
            name: true,
          },
        },
      },
    });
  }),
```

getAllでOwnerの名前も一緒に取得したいのですが

https://wp-kyoto.net/prisma-select-relational-table-data/

をみるとPrismaでリレーション先のデータも取得したい場合、includeを指定するとリレーション先丸ごと取得、selectを指定すると特定のカラムのみ取得できるようです。
ということはincludeを指定してPrismaの結果をそのまま返すとUser.crypted_passwordも返してしまいます。いくら暗号化されているとはいえさすがにまずい。
selectで、必要なカラムのみ取得するように指定するか、includeで指定して取得したデータから必要な分だけ返すようにするかする必要がありそうです。今回はとりあえずselectでowner.nameのみ取得するように指定しました。

## Unit Test中に発行されるSQLをみたい

テストコードを追加して動作することを確認します。
ただ、開発用サーバを利用しているときは実際に実行されているSQLがコンソールに表示されるのですが、UnitTest実行中は表示されないみたいです。PrismaClientを生成しているのはsrc/server/db/client.tsで、env.NODE_ENVがdevelopmentの時だけ、queryが表示されるようになっていました。testの時も表示されるよう修正します。
```diff_typescript
@@ -11,7 +11,9 @@ export const prisma =
   global.prisma ||
   new PrismaClient({
     log:
-      env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
+      env.NODE_ENV === "development" || env.NODE_ENV === "test"
+        ? ["query", "error", "warn"]
```
実際に実行されるログを見てみると、リレーション先のデータは別のQueryで取得してるみたいです。
N:1リレーションの場合、Left Joinすれば1回のQueryで取得できそうですが、そのような方法もあるのでしょうか。

```
stdout | src/server/trpc/router/todo.test.ts > getAll > getAll
prisma:query SELECT `main`.`Todo`.`id`, `main`.`Todo`.`title`, `main`.`Todo`.`description`, `main`.`Todo`.`ownerId` FROM `main`.`Todo` WHERE 1=1 LIMIT ? OFFSET ?
prisma:query SELECT `main`.`User`.`id`, `main`.`User`.`name` FROM `main`.`User` WHERE `main`.`User`.`id` IN (?) LIMIT ? OFFSET ?
```

今までの変更は以下の通りです。前回まではすべての変更を説明していましたが、長くなりすぎるので一部省くことにしました。

https://github.com/bioflowy/t3-todo-example/commit/c6e0bc4c90fc00596701d849dd0baacc35642f21