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.