mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2025-12-13 01:24:44 -05:00
- 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
208 lines
5.8 KiB
TypeScript
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;
|
|
}),
|
|
});
|