Skip to main content

Adding a New Module

Follow the V-Model workflow for implementing any feature in Tonnex.

Step 1: Define Validators

Create Zod schemas in packages/utils/src/validators/<module>.ts:
import { z } from "zod";

export const createVehicleSchema = z.object({
  regNumber: z.string().min(1, "Registration number is required"),
  type: z.enum(["truck", "trailer", "tanker"]),
  make: z.string().min(1),
  model: z.string().min(1),
  year: z.number().min(1900).max(2100),
});

export type CreateVehicleInput = z.infer<typeof createVehicleSchema>;

Step 2: Define Database Schema

Create the Drizzle schema in packages/db/src/schema/<module>.ts:
import { pgTable, uuid, text, timestamp, varchar, integer } from "drizzle-orm/pg-core";
import { organizations } from "./auth";

export const vehicles = pgTable("vehicles", {
  id: uuid("id").defaultRandom().primaryKey(),
  orgId: uuid("org_id").references(() => organizations.id).notNull(), // CRITICAL
  regNumber: varchar("reg_number", { length: 20 }).notNull(),
  type: varchar("type", { length: 50 }).notNull(),
  make: varchar("make", { length: 100 }).notNull(),
  model: varchar("model", { length: 100 }).notNull(),
  year: integer("year").notNull(),
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
});
Then export from packages/db/src/schema/index.ts and run:
pnpm db:generate
pnpm db:push

Step 3: Create Server Actions

In apps/web/actions/<module>.ts:
"use server";

import { db } from "@tonnex/db";
import { vehicles } from "@tonnex/db/schema";
import { createClient } from "@/lib/supabase/server";
import { revalidatePath } from "next/cache";

export async function createVehicle(data: CreateVehicleInput) {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) throw new Error("Unauthorized");

  await db.insert(vehicles).values({
    ...data,
    orgId: "current-org-id", // resolve from session/context
  });

  revalidatePath("/vehicles");
}

Step 4: Build the Page

In apps/web/app/(dashboard)/vehicles/page.tsx, use DiceUI Data Table for the list view.

Step 5: Update Documentation

Update this documentation site with the new module’s API reference and usage guide.
Every table must include org_id for RLS. Tables without org_id will not be protected by Row Level Security.