驴Qu茅 es la autorizaci贸n?
Autorizaci贸n es el proceso mediante el cual determinamos si un usuario tiene los permisos para realizar una acci贸n. En este momento tenemos autenticaci贸n, pero requerimos que las llamadas ademas de la autenticaci贸n tengan la autorizaci贸n incluida.
Adjuntar el Auth State (Estado de la Autenticaci贸n) al Context (Contexto) de TRPC
-
Abrimos el archivo src/server/api/trpc.ts y vamos a agregar el usuario de
Clerk
al contexto de TRPC. Debido a que Clerk utiliza JWT, puede verificar que estas llamadas que se relizan desde el cliente, sean solicitadas realmente por el usuario que dice solicitarlas.import { getAuth } from '@clerk/nextjs/server' // ... export const createTRPCContext = (opts: CreateNextContextOptions) => { const { req } = opts const { userId } = getAuth(req) if (!userId) { throw new TRPCError({ code: 'UNAUTHORIZED' }) } const context = createInnerTRPCContext({}) return { ...context, userId, } }
-
Una ves actualizada la funci贸n que crea el context de TRPC, vamos a crear un middleware de TRPC que force al usuario a estar autenticado y creamos un nuevo procedure privado llamado
privateProcedure
.const enforceUserIsAuth = t.middleware(async function ({ ctx: { userId }, next, }) { if (!userId) { throw new TRPCError({ code: 'UNAUTHORIZED' }) } return next({ ctx: { currentUser: userId } }) }) export const privateProcedure = t.procedure.use(enforceUserIsAuth)
La librer铆a Zod
Zod es una librer铆a que permite realizar validaciones completas utilizando una sentencia encadenada, por ejemplo.
import { z } from 'zod'
// validar el env铆o de un formulario que contenga un campo "content" el cual es:
// - tipo string
// - contiene solo emojis
// - entre 1 y 280 caracteres
z.object({
content: z.string().emoji().min(1).max(280),
})
Implementar el m茅todo create para el router posts
Ahora utilizando zod y el privateprocedure que acabamos de crear, implementamos el m茅todo create dentro del router posts.
import { z } from 'zod'
// ...
export const postsRouter = createTRPCRouter({
// ...
create: privateProcedure
.input(
z.object({
content: z.string().emoji().min(1).max(280),
})
)
.mutation(async ({ ctx, input }) => {
const authorId = ctx.userId
await ctx.prisma.post.create({
data: {
content: input.content,
authorId,
},
})
}),
})
Invocar el m茅todo create desde el componente CreatePostWizard
Implementamos la llamada al mutator posts:create()
desde el componente CreatePostWizard
.
const CreatePostWizard = () => {
const { user } = useUser()
const [input, setInput] = useState<string>('')
const { mutate, isLoading: isPosting } = api.posts.create.useMutation({})
if (!user) {
return null
}
if (isPosting) {
return <LoadingPage />
}
return (
<div className="flex w-full gap-3">
<Image
src={user.imageUrl}
width={120}
height={120}
alt="Profile image"
className="h-14 w-14 rounded-full"
/>
<input
placeholder="Type some emojis"
className="grow bg-transparent outline-none"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<button onClick={() => mutate({ content: input })}>Post</button>
</div>
)
}
Hemos complementado el c贸digo de manera que cuando se presione el bot贸n se invoque el mutator create, sin embargo para poder ver el resultado a煤n es necesario refrescar la p谩gina.
Invalidar state para el query getAll
El listado de posts esta asociado al query posts:getAll
, para que este se refresque autom谩ticamente, necesitamos invalidarlo cuando el proceso del mutator posts:create
se complete exitosamente, as铆 como en cualquier otro caso en el cual se altere el n煤mero de posts en la base de datos.
Para ello vamos a especificar el m茅todo onSuccess
del mutator posts:create
.
const ctx = api.useContext()
// ...
const { mutate, isLoading: isPosting } = api.posts.create.useMutation({
onSuccess: () => {
setInput('')
void ctx.posts.getAll.invalidate()
},
})
Aqu铆 lo que hemos hecho es invalidar el query
posts:getAll
de manera que al completar el env铆o de un nuevo post, se ejecute el query nuevamente.
Asegurarse del ordenamiento de los posts
Como deseamos que el orden de los posts sea del mas reciente al mas antiguo, vamos a actualizar tambi茅n el query para que siga este ordenamiento.
// src/server/api/routers/post.ts
export const postsRouter = createTRPCRouter({
getAll: publicProcedure.query(async ({ ctx }) => {
const posts = await ctx.prisma.post.findMany({
take: 100,
orderBy: {
createdAt: 'desc',
},
})
// ...
return updatedPosts
}),
// ...
})
Hemos podido utilizar el context de trpc para incluir dentro de este la informaci贸n de la sesi贸n del usuario, as铆 como la librer铆a Zod para realizar validaciones de los datos de enviados desde un formulario.