feat(WEB-127): add trpc to handle requests both serverside and clientside
This commit is contained in:
27
server/context.ts
Normal file
27
server/context.ts
Normal 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
28
server/errors.ts
Normal 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
10
server/index.ts
Normal 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
|
||||
5
server/routers/user/index.ts
Normal file
5
server/routers/user/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { mergeRouters } from "@/server/trpc"
|
||||
|
||||
import { userQueryRouter } from "./query"
|
||||
|
||||
export const userRouter = mergeRouters(userQueryRouter)
|
||||
3
server/routers/user/input.ts
Normal file
3
server/routers/user/input.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
/**
|
||||
* Add route inputs (both query & mutation)
|
||||
*/
|
||||
3
server/routers/user/mutation.ts
Normal file
3
server/routers/user/mutation.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
/**
|
||||
* Add User mutations
|
||||
*/
|
||||
27
server/routers/user/output.ts
Normal file
27
server/routers/user/output.ts
Normal 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(),
|
||||
})
|
||||
25
server/routers/user/query.ts
Normal file
25
server/routers/user/query.ts
Normal 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
2
server/transformer.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import superjson from "superjson"
|
||||
export const transformer = superjson
|
||||
35
server/trpc.ts
Normal file
35
server/trpc.ts
Normal 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,
|
||||
},
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user