93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
import { initTRPC, TRPCError } from "@trpc/server";
|
|
import type { TrpcContext } from "./context.js";
|
|
import { isSubscriptionActive } from "../db.js";
|
|
import { childLogger } from "./logger.js";
|
|
|
|
const trpcLog = childLogger("trpc");
|
|
|
|
const t = initTRPC.context<TrpcContext>().create({
|
|
errorFormatter({ shape }) {
|
|
return shape;
|
|
},
|
|
});
|
|
|
|
export const router = t.router;
|
|
export const middleware = t.middleware;
|
|
|
|
// Timing middleware: logs every procedure call with its duration. Errors are
|
|
// re-thrown so the existing error handlers in the express adapter still fire.
|
|
const timing = t.middleware(async ({ path, type, next, ctx }) => {
|
|
const start = Date.now();
|
|
try {
|
|
const result = await next();
|
|
const durationMs = Date.now() - start;
|
|
if (result.ok) {
|
|
trpcLog.debug({ path, type, durationMs, userId: ctx.user?.id ?? null }, "procedure ok");
|
|
} else {
|
|
trpcLog.warn(
|
|
{ path, type, durationMs, userId: ctx.user?.id ?? null, code: result.error.code },
|
|
"procedure error"
|
|
);
|
|
}
|
|
return result;
|
|
} catch (err) {
|
|
const durationMs = Date.now() - start;
|
|
trpcLog.error({ err, path, type, durationMs, userId: ctx.user?.id ?? null }, "procedure threw");
|
|
throw err;
|
|
}
|
|
});
|
|
|
|
export const publicProcedure = t.procedure.use(timing);
|
|
|
|
const isAuthed = t.middleware(({ ctx, next }) => {
|
|
if (!ctx.user) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Authentication required" });
|
|
}
|
|
if (ctx.user.disabled) {
|
|
throw new TRPCError({ code: "FORBIDDEN", message: "Compte désactivé" });
|
|
}
|
|
return next({
|
|
ctx: {
|
|
...ctx,
|
|
user: ctx.user,
|
|
},
|
|
});
|
|
});
|
|
|
|
export const protectedProcedure = t.procedure.use(isAuthed);
|
|
|
|
const isAdmin = t.middleware(({ ctx, next }) => {
|
|
if (!ctx.user) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Authentication required" });
|
|
}
|
|
if (ctx.user.disabled) {
|
|
throw new TRPCError({ code: "FORBIDDEN", message: "Compte désactivé" });
|
|
}
|
|
if (ctx.user.role !== "admin") {
|
|
throw new TRPCError({ code: "FORBIDDEN", message: "Accès réservé aux administrateurs" });
|
|
}
|
|
return next({
|
|
ctx: {
|
|
...ctx,
|
|
user: ctx.user,
|
|
},
|
|
});
|
|
});
|
|
|
|
export const adminProcedure = t.procedure.use(isAdmin);
|
|
|
|
const requireActiveSubscription = t.middleware(async ({ ctx, next }) => {
|
|
if (!ctx.user) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Authentication required" });
|
|
}
|
|
const active = await isSubscriptionActive(ctx.user.id);
|
|
if (!active) {
|
|
throw new TRPCError({
|
|
code: "FORBIDDEN",
|
|
message: "Subscription expired or inactive",
|
|
});
|
|
}
|
|
return next({ ctx: { ...ctx, user: ctx.user } });
|
|
});
|
|
|
|
export const subscriptionProcedure = t.procedure.use(requireActiveSubscription);
|