Files
beenvoice/src/server/api/routers/businesses.ts
Sean O'Connor a1b40e7a9c Add Turso/Vercel deployment configuration
- Updated database connection to support Turso auth token
- Added vercel.json with bun build configuration
- Updated environment schema for production deployment
- Added new features and components for production readiness
2025-07-12 01:42:43 -04:00

208 lines
5.8 KiB
TypeScript

import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import { businesses } from "~/server/db/schema";
import { eq, and, desc } from "drizzle-orm";
import { invoices } from "~/server/db/schema";
import { sql } from "drizzle-orm";
const businessSchema = z.object({
name: z.string().min(1, "Business name is required"),
email: z.string().email().optional().or(z.literal("")),
phone: z.string().optional().or(z.literal("")),
addressLine1: z.string().optional().or(z.literal("")),
addressLine2: z.string().optional().or(z.literal("")),
city: z.string().optional().or(z.literal("")),
state: z.string().optional().or(z.literal("")),
postalCode: z.string().optional().or(z.literal("")),
country: z.string().optional().or(z.literal("")),
website: z.string().url().optional().or(z.literal("")),
taxId: z.string().optional().or(z.literal("")),
logoUrl: z.string().optional().or(z.literal("")),
isDefault: z.boolean().default(false),
});
export const businessesRouter = createTRPCRouter({
// Get all businesses for the current user
getAll: protectedProcedure.query(async ({ ctx }) => {
const userBusinesses = await ctx.db
.select()
.from(businesses)
.where(eq(businesses.createdById, ctx.session.user.id))
.orderBy(desc(businesses.isDefault), desc(businesses.createdAt));
return userBusinesses;
}),
// Get a single business by ID
getById: protectedProcedure
.input(z.object({ id: z.string() }))
.query(async ({ ctx, input }) => {
const business = await ctx.db
.select()
.from(businesses)
.where(
and(
eq(businesses.id, input.id),
eq(businesses.createdById, ctx.session.user.id)
)
)
.limit(1);
return business[0];
}),
// Get default business for the current user
getDefault: protectedProcedure.query(async ({ ctx }) => {
const defaultBusiness = await ctx.db
.select()
.from(businesses)
.where(
and(
eq(businesses.createdById, ctx.session.user.id),
eq(businesses.isDefault, true)
)
)
.limit(1);
return defaultBusiness[0];
}),
// Create a new business
create: protectedProcedure
.input(businessSchema)
.mutation(async ({ ctx, input }) => {
// If this is the first business or isDefault is true, unset other defaults
if (input.isDefault) {
await ctx.db
.update(businesses)
.set({ isDefault: false })
.where(eq(businesses.createdById, ctx.session.user.id));
}
const [newBusiness] = await ctx.db
.insert(businesses)
.values({
...input,
createdById: ctx.session.user.id,
})
.returning();
return newBusiness;
}),
// Update an existing business
update: protectedProcedure
.input(
z.object({
id: z.string(),
...businessSchema.shape,
})
)
.mutation(async ({ ctx, input }) => {
const { id, ...updateData } = input;
// If setting this business as default, unset other defaults
if (updateData.isDefault) {
await ctx.db
.update(businesses)
.set({ isDefault: false })
.where(
and(
eq(businesses.createdById, ctx.session.user.id),
eq(businesses.id, id)
)
);
}
const [updatedBusiness] = await ctx.db
.update(businesses)
.set({
...updateData,
updatedAt: new Date(),
})
.where(
and(
eq(businesses.id, id),
eq(businesses.createdById, ctx.session.user.id)
)
)
.returning();
if (!updatedBusiness) {
throw new Error("Business not found or you don't have permission to update it");
}
return updatedBusiness;
}),
// Delete a business
delete: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => {
// Check if business exists and belongs to user
const business = await ctx.db
.select()
.from(businesses)
.where(
and(
eq(businesses.id, input.id),
eq(businesses.createdById, ctx.session.user.id)
)
)
.limit(1);
if (!business[0]) {
throw new Error("Business not found or you don't have permission to delete it");
}
// Check if this business has any invoices
const invoiceCount = await ctx.db
.select({ count: sql<number>`count(*)` })
.from(invoices)
.where(eq(invoices.businessId, input.id));
if (invoiceCount[0] && invoiceCount[0].count > 0) {
throw new Error("Cannot delete business that has invoices. Please delete all invoices first.");
}
await ctx.db
.delete(businesses)
.where(
and(
eq(businesses.id, input.id),
eq(businesses.createdById, ctx.session.user.id)
)
);
return { success: true };
}),
// Set a business as default
setDefault: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => {
// First, unset all other defaults for this user
await ctx.db
.update(businesses)
.set({ isDefault: false })
.where(eq(businesses.createdById, ctx.session.user.id));
// Then set the specified business as default
const [updatedBusiness] = await ctx.db
.update(businesses)
.set({ isDefault: true })
.where(
and(
eq(businesses.id, input.id),
eq(businesses.createdById, ctx.session.user.id)
)
)
.returning();
if (!updatedBusiness) {
throw new Error("Business not found or you don't have permission to update it");
}
return updatedBusiness;
}),
});