feat(WEB-127): add trpc to handle requests both serverside and clientside

This commit is contained in:
Simon Emanuelsson
2024-03-20 16:39:11 +01:00
parent 2087ac6c91
commit ec4da5798b
31 changed files with 422 additions and 40 deletions

27
server/context.ts Normal file
View File

@@ -0,0 +1,27 @@
import { auth } from "@/auth"
type CreateContextOptions = {
auth: typeof auth
}
/** Use this helper for:
* - testing, where we dont have to Mock Next.js' req/res
* - trpc's `createSSGHelpers` where we don't have req/res
**/
export function createContextInner(opts: CreateContextOptions) {
return {
auth: opts.auth,
}
}
/**
* This is the actual context you'll use in your router
* @link https://trpc.io/docs/context
**/
export function createContext() {
return createContextInner({
auth,
})
}
export type Context = ReturnType<typeof createContext>

28
server/errors.ts Normal file
View File

@@ -0,0 +1,28 @@
import { TRPCError } from "@trpc/server"
import {
TRPC_ERROR_CODES_BY_NUMBER,
TRPC_ERROR_CODES_BY_KEY,
} from "@trpc/server/rpc"
export function unauthorizedError() {
return new TRPCError({
code: TRPC_ERROR_CODES_BY_NUMBER[TRPC_ERROR_CODES_BY_KEY.UNAUTHORIZED],
message: `Authorization required!`,
})
}
export function internalServerError() {
return new TRPCError({
code: TRPC_ERROR_CODES_BY_NUMBER[
TRPC_ERROR_CODES_BY_KEY.INTERNAL_SERVER_ERROR
],
message: `Internal Server Error!`,
})
}
export function badRequestError(msg = "Bad request!") {
return new TRPCError({
code: TRPC_ERROR_CODES_BY_NUMBER[TRPC_ERROR_CODES_BY_KEY.BAD_REQUEST],
message: msg,
})
}

10
server/index.ts Normal file
View File

@@ -0,0 +1,10 @@
import { router } from "./trpc"
/** Routers */
import { userRouter } from "./routers/user"
export const appRouter = router({
user: userRouter,
})
export type AppRouter = typeof appRouter

View File

@@ -0,0 +1,5 @@
import { mergeRouters } from "@/server/trpc"
import { userQueryRouter } from "./query"
export const userRouter = mergeRouters(userQueryRouter)

View File

@@ -0,0 +1,3 @@
/**
* Add route inputs (both query & mutation)
*/

View File

@@ -0,0 +1,3 @@
/**
* Add User mutations
*/

View File

@@ -0,0 +1,27 @@
import { z } from "zod"
/**
* Return value from jsonplaceholder.com/users/1
* Add proper user object expectation when fetching
* from Scandic API
*/
export const getUserSchema = z.object({
address: z.object({
city: z.string(),
geo: z.object({}),
street: z.string(),
suite: z.string(),
zipcode: z.string(),
}),
company: z.object({
bs: z.string(),
catchPhrase: z.string(),
name: z.string(),
}),
email: z.string().email(),
id: z.number(),
name: z.string(),
phone: z.string(),
username: z.string(),
website: z.string(),
})

View File

@@ -0,0 +1,25 @@
import { badRequestError, internalServerError } from "@/server/errors"
import { protectedProcedure, router } from "@/server/trpc"
import { getUserSchema } from "./output"
export const userQueryRouter = router({
get: protectedProcedure.query(async function (opts) {
// TODO: Make request to get user data from Scandic API
const response = await fetch(
"https://jsonplaceholder.typicode.com/users/1",
{
cache: "no-store",
}
)
if (!response.ok) {
throw internalServerError()
}
const json = await response.json()
const validJson = getUserSchema.parse(json)
if (!validJson) {
throw badRequestError()
}
return validJson
}),
})

2
server/transformer.ts Normal file
View File

@@ -0,0 +1,2 @@
import superjson from "superjson"
export const transformer = superjson

35
server/trpc.ts Normal file
View File

@@ -0,0 +1,35 @@
import { initTRPC } from "@trpc/server"
import { env } from "@/env/server"
import { transformer } from "./transformer"
import { unauthorizedError } from "./errors"
import type { Context } from "./context"
import type { Meta } from "@/types/trpc/meta"
const t = initTRPC.context<Context>().meta<Meta>().create({ transformer })
export const { createCallerFactory, mergeRouters, router } = t
export const publicProcedure = t.procedure
export const protectedProcedure = t.procedure.use(async function (opts) {
const authRequired = opts.meta?.authRequired ?? true
const session = await opts.ctx.auth()
if (authRequired) {
if (!session?.user) {
throw unauthorizedError()
}
} else {
if (env.NODE_ENV === "development") {
console.info(
`❌❌❌❌ You are opting out of authorization, if its done on purpose maybe you should use the publicProcedure instead. ❌❌❌❌`
)
console.info(`path: ${opts.path} | type: ${opts.type}`)
}
}
return opts.next({
ctx: {
session,
},
})
})