管理者権限を付与する の変更点
更新- 追加された行はこの色です。
- 削除された行はこの色です。
- プログラミング/svelte/管理者権限を付与する へ行く。
- プログラミング/svelte/管理者権限を付与する の差分を削除
[[プログラミング/svelte]]
* 権限を管理する [#a538926a]
AuthUser と 多対多 関係を持つ Role を導入する。
prisma/schema.prisma
   model User {
   ...
 +   roles         Role[]
   }
  
 + model Role {
 +   id             String   @id @default(uuid())
 +   name           String   @unique
 + 
 +   users          User[]
 + }
マイグレーションする。
 LANG: console
 $ pnpm prisma migrate dev --name "add Role table"
Prisma で多対多関係を扱うためのコードは結構煩雑になるようなので、
Role を使うためのユーティリティ関数を db に持たせる。
このやり方が良いのかは疑問が残る?~
→ [[やはり後ほど src/lib/server/role.ts に移した>プログラミング/svelte/コード配置の整理]]
→ [[やはり後ほど src/lib/server/role.ts に移した>プログラミング/svelte/コード配置の整理#u0f474f2]]
src/lib/server/db.ts
 LANG: ts
 import { PrismaClient } from '@prisma/client';
 
 export class ExtendedPrismaClient extends PrismaClient {
   constructor() { super() }
 
   // userId が undefined なら権限は空とみなされる
   async getRoles(userId: string | undefined) {
     if(!userId) {
       return []
     }
     return (await this.user.findUnique({
       where: {id: userId},
       select: {roles: true}
     }))?.roles || []
   }
 
   async getRolesString(userId: string | undefined) {
     return (await this.getRoles(userId)).map((role)=> role.name);
   }
 
   async hasRole(userId: string | undefined, role: string) {
     return (await this.getRolesString(userId)).includes(role);
   }
 
   async addRoles(userId: string, ...roles: string[]) {
     await this.user.update({
       where: { id: userId },
       data: {
         roles: {
           connectOrCreate: roles.map(role=>
             ({where: {name: role}, create: {name: role}})
           )
         },
       },
     })
   }
 
   async removeRoles(userId: string, ...roles: string[]) {
     await this.user.update({
       where: { id: userId },
       data: {
         roles: {
           deleteMany: roles.map(role=>({name: role}))
         },
       },
     })
   }
 }
 
 export const db = new ExtendedPrismaClient();
Prisma の言語仕様を見ていると TypeScript と VSCode の存在がいかに偉大か実感する。
こんな DSL はエディタ上でエラー検出してくれなきゃ、とてもじゃないけど書けない。
ところで、
 LANG: ts
 roles.map(role=>({name: role}))
などという記述に注意が必要。これを
 LANG: ts
 roles.map(role=>{name: role})
と書いてしまうと role=>{ の { がブロックの開始として解釈されてしまい、うまく行かない。
(その場合 name: はラベルとして解釈される)
JavaScriptのアロー関数でオブジェクトを返す方法~
https://dev.classmethod.jp/articles/arrow-func-return-object/
そこで一見すると無駄に見える括弧で括っている。
サインアップ時、最初のユーザーには admin 権限を持たせることにする。
src/routes/account/(logout)/new/+page.server.ts
 + import { db } from '$lib/server/db'
 ...
 
 +       // 最初のユーザーには admin 権限を持たせる
 +       if(await db.user.count() == 1) {
 +         await db.addRoles(user.userId, 'admin')
 +       }
(admin) を含むパスには admin 権限を持っているユーザーしかアクセスできない。
src/hooks.server.ts
 LANG:ts
 +   if (event.route.id?.match(/\/\(admin\)\//) && !event.locals.session?.user) {
 +     setFlash({ type: 'error', message: '管理者ユーザーのみアクセスできます' }, event);
 +     throw redirect(302, path('/session/new'));
 +   }
 LANG: console
 $ git add . && git commit -m "管理者ユーザーの仕組みを導入"
Counter: 774 (from 2010/06/03), 
today: 2,
yesterday: 2