lifetracker/packages/trpc/index.ts

85 lines
2.1 KiB
TypeScript

import { initTRPC, TRPCError } from "@trpc/server";
import { SuperJSON } from "superjson";
import { ZodError } from "zod";
import type { db } from "@lifetracker/db";
import serverConfig from "@lifetracker/shared/config";
interface User {
id: string;
name?: string | null | undefined;
email?: string | null | undefined;
role: "admin" | "user" | null;
}
export interface Context {
user: User | null;
db: typeof db;
req: {
ip: string | null;
};
}
export interface AuthedContext {
user: User;
db: typeof db;
req: {
ip: string | null;
};
}
// Avoid exporting the entire t-object
// since it's not very descriptive.
// For instance, the use of a t variable
// is common in i18n libraries.
const t = initTRPC.context<Context>().create({
transformer: SuperJSON,
errorFormatter(opts) {
const { shape, error } = opts;
return {
...shape,
data: {
...shape.data,
zodError:
error.code === "BAD_REQUEST" && error.cause instanceof ZodError
? error.cause.flatten()
: null,
},
};
},
});
export const createCallerFactory = t.createCallerFactory;
// Base router and procedure helpers
export const router = t.router;
export const procedure = t.procedure.use(function isDemoMode(opts) {
if (serverConfig.demoMode && opts.type == "mutation") {
throw new TRPCError({
message: "Mutations are not allowed in demo mode",
code: "FORBIDDEN",
});
}
return opts.next();
});
export const publicProcedure = procedure;
export const authedProcedure = procedure.use(function isAuthed(opts) {
const user = opts.ctx.user;
if (!user?.id) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return opts.next({
ctx: {
user,
},
});
});
export const adminProcedure = authedProcedure.use(function isAdmin(opts) {
const user = opts.ctx.user;
if (user.role != "admin") {
throw new TRPCError({ code: "FORBIDDEN" });
}
return opts.next(opts);
});