Documentation

Server

Backend Patterns

Database helpers, tRPC router procedures, IP extraction utilities, and role-based middleware patterns for the broker management system.

Database Helpers

Add these functions to server/db.ts. They provide CRUD operations and counter increments for broker landing pages.

server/db.ts — Broker Landing Pages CRUD
// ─── Broker Landing Pages ───────────────────────────────────────
export async function getBrokerLandingPageBySlug(slug: string) {
  const rows = await db.select().from(brokerLandingPages)
    .where(eq(brokerLandingPages.slug, slug)).limit(1);
  return rows[0] ?? null;
}

export async function getBrokerLandingPageByBrokerId(brokerId: number) {
  const rows = await db.select().from(brokerLandingPages)
    .where(eq(brokerLandingPages.brokerId, brokerId)).limit(1);
  return rows[0] ?? null;
}

export async function getAllBrokerLandingPages() {
  return db.select().from(brokerLandingPages)
    .orderBy(brokerLandingPages.createdAt);
}

export async function createBrokerLandingPage(data: InsertBrokerLandingPage) {
  const [result] = await db.insert(brokerLandingPages).values(data);
  return result.insertId;
}

export async function updateBrokerLandingPage(
  id: number,
  data: Partial<InsertBrokerLandingPage>
) {
  await db.update(brokerLandingPages)
    .set(data)
    .where(eq(brokerLandingPages.id, id));
}

export async function deleteBrokerLandingPage(id: number) {
  await db.delete(brokerLandingPages)
    .where(eq(brokerLandingPages.id, id));
}

export async function incrementLandingPageViews(id: number) {
  await db.update(brokerLandingPages)
    .set({ viewCount: sql`${brokerLandingPages.viewCount} + 1` })
    .where(eq(brokerLandingPages.id, id));
}

export async function incrementLandingPageApplications(id: number) {
  await db.update(brokerLandingPages)
    .set({ applicationCount: sql`${brokerLandingPages.applicationCount} + 1` })
    .where(eq(brokerLandingPages.id, id));
}

IP-Aware Task Helpers

These helpers extend existing task operations to record the client's IP address alongside each action.

server/db.ts — IP-Aware Task Functions
export async function createTaskWithIp(
  data: InsertTask & { createdByIp?: string }
) {
  const [result] = await db.insert(applicationTasks).values(data);
  return result.insertId;
}

export async function updateTaskWithIp(
  id: number,
  data: Partial<InsertTask> & { lastUpdatedByIp?: string }
) {
  await db.update(applicationTasks)
    .set(data)
    .where(eq(applicationTasks.id, id));
}

export async function completeTaskWithIp(id: number, ip?: string) {
  await db.update(applicationTasks).set({
    isCompleted: true,
    completedAt: new Date(),
    completedByIp: ip,
  }).where(eq(applicationTasks.id, id));
}

IP Extraction Helper

Place this utility at the top of your router file. It extracts the client's real IP from proxy headers, falling back to the direct connection address.

server/routers.ts — IP Extraction
function getClientIp(req: any): string | undefined {
  const forwarded = req.headers?.["x-forwarded-for"];
  if (typeof forwarded === "string")
    return forwarded.split(",")[0].trim();
  return req.ip || req.connection?.remoteAddress;
}

Broker Landing Router

The complete tRPC router with public, admin, and broker self-service procedures. Register it in the main appRouter.

ProcedureAuthPurpose
getBySlugpublicFetch page by slug, increment views, return null if inactive
listAlladminList all landing pages
createadminCreate page, reject duplicate slugs
adminUpdateadminUpdate any page fields
deleteadminDelete a page
getMyPageprotectedBroker gets own page by userId
updateMyPageprotectedBroker edits own page (not slug/active)
server/routers.ts — Broker Landing Router
const brokerLandingRouter = t.router({
  // Public: get page by slug (increments view count)
  getBySlug: publicProcedure
    .input(z.object({ slug: z.string() }))
    .query(async ({ input }) => {
      const page = await getBrokerLandingPageBySlug(input.slug);
      if (!page || !page.isActive) return null;
      incrementLandingPageViews(page.id); // fire-and-forget
      return page;
    }),

  // Admin: list all, create, update, delete
  listAll: adminProcedure.query(() => getAllBrokerLandingPages()),

  create: adminProcedure
    .input(z.object({
      brokerId: z.number(),
      slug: z.string().min(2).max(128),
      displayName: z.string().optional(),
      displayEmail: z.string().email().optional(),
      // ... other optional fields
    }))
    .mutation(async ({ input }) => {
      const existing = await getBrokerLandingPageBySlug(input.slug);
      if (existing)
        throw new TRPCError({ code: "CONFLICT", message: "Slug already in use" });
      const id = await createBrokerLandingPage(input);
      return { id };
    }),

  adminUpdate: adminProcedure
    .input(z.object({ id: z.number(), /* partial fields */ }))
    .mutation(async ({ input }) => {
      const { id, ...data } = input;
      await updateBrokerLandingPage(id, data);
      return { success: true };
    }),

  delete: adminProcedure
    .input(z.object({ id: z.number() }))
    .mutation(async ({ input }) => {
      await deleteBrokerLandingPage(input.id);
      return { success: true };
    }),

  // Broker self-service
  getMyPage: protectedProcedure.query(async ({ ctx }) => {
    return getBrokerLandingPageByBrokerId(ctx.user.id);
  }),

  updateMyPage: protectedProcedure
    .input(z.object({
      headline: z.string().optional(),
      bio: z.string().optional(),
      // ... other editable fields
    }))
    .mutation(async ({ ctx, input }) => {
      const page = await getBrokerLandingPageByBrokerId(ctx.user.id);
      if (!page)
        throw new TRPCError({ code: "NOT_FOUND", message: "No landing page found" });
      await updateBrokerLandingPage(page.id, input);
      return { success: true };
    }),
});

Role-Based Middleware

server/routers.ts — Admin Middleware
const adminProcedure = protectedProcedure.use(({ ctx, next }) => {
  if (ctx.user.role !== "admin")
    throw new TRPCError({ code: "FORBIDDEN" });
  return next({ ctx });
});

Registering in Main Router

server/routers.ts — App Router
export const appRouter = t.router({
  // ... existing routers
  brokerLanding: brokerLandingRouter,
});