import { initTRPC } from "@trpc/server" import { experimental_nextAppDirCaller } from "@trpc/server/adapters/next-app-dir" import { ZodError } from "zod" import { env } from "@/env/server" import { badRequestError, internalServerError, sessionExpiredError, unauthorizedError, } from "./errors/trpc" import { type Context, createContext } from "./context" import { getServiceToken } from "./tokenManager" import { transformer } from "./transformer" import { langInput } from "./utils" import type { Session } from "next-auth" import type { Meta } from "@/types/trpc/meta" const t = initTRPC .context() .meta() .create({ transformer, errorFormatter({ shape, error }) { return { ...shape, data: { ...shape.data, zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, }, } }, }) export const { createCallerFactory, mergeRouters, router } = t export const publicProcedure = t.procedure export const contentstackBaseProcedure = t.procedure.use(async function (opts) { if (!opts.ctx.lang) { // When fetching data client side with TRPC we don't pass through middlewares and therefore do not get the lang through headers // We can then pass lang as an input in the request and set it to the context in the procedure const input = await opts.getRawInput() const parsedInput = langInput.safeParse(input) if (!parsedInput.success) { throw badRequestError("Missing Lang in tRPC context") } return opts.next({ ctx: { lang: parsedInput.data.lang, }, }) } return opts.next({ ctx: { lang: opts.ctx.lang, }, }) }) export const contentstackExtendedProcedureUID = contentstackBaseProcedure.use( async function (opts) { if (!opts.ctx.uid) { throw badRequestError("Missing UID in tRPC context") } return opts.next({ ctx: { uid: opts.ctx.uid, }, }) } ) export const protectedProcedure = t.procedure.use(async function (opts) { const authRequired = opts.meta?.authRequired ?? true const session = await opts.ctx.auth() if (!authRequired && 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}`) } if (!session) { throw unauthorizedError() } if (session?.error === "RefreshAccessTokenError") { throw sessionExpiredError() } return opts.next({ ctx: { session, }, }) }) export const safeProtectedProcedure = t.procedure.use(async function (opts) { const authRequired = opts.meta?.authRequired ?? true let session: Session | null = await opts.ctx.auth() if (!authRequired && 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}`) } if (!session || session.error === "RefreshAccessTokenError") { session = null } return opts.next({ ctx: { session, }, }) }) export const serviceProcedure = t.procedure.use(async function (opts) { const { access_token } = await getServiceToken() if (!access_token) { throw internalServerError(`[serviceProcedure] No service token`) } return opts.next({ ctx: { serviceToken: access_token, }, }) }) export const serverActionProcedure = t.procedure.experimental_caller( experimental_nextAppDirCaller({ createContext, normalizeFormData: true, }) ) export const serviceServerActionProcedure = serverActionProcedure.use( async (opts) => { const { access_token } = await getServiceToken() if (!access_token) { throw internalServerError( "[serviceServerActionProcedure]: No service token" ) } return opts.next({ ctx: { serviceToken: access_token, }, }) } ) export const protectedServerActionProcedure = serverActionProcedure.use( async (opts) => { const session = await opts.ctx.auth() if (!session) { throw unauthorizedError() } if (session && session.error === "RefreshAccessTokenError") { throw sessionExpiredError() } return opts.next({ ctx: { ...opts.ctx, session, }, }) } ) // NOTE: This is actually safe to use, just the implementation could change // in minor version bumps. Please read: https://trpc.io/docs/faq#unstable export const contentStackUidWithServiceProcedure = contentstackExtendedProcedureUID.unstable_concat(serviceProcedure) export const contentStackBaseWithServiceProcedure = contentstackBaseProcedure.unstable_concat(serviceProcedure) export const contentStackBaseWithProtectedProcedure = contentstackBaseProcedure.unstable_concat(protectedProcedure) export const safeProtectedServiceProcedure = safeProtectedProcedure.unstable_concat(serviceProcedure)