From cbd2fb2921ab2939ff689f41b7907dfdf34e26af Mon Sep 17 00:00:00 2001 From: Daniel Kirby Date: Sat, 6 Jun 2026 14:14:12 +0100 Subject: [PATCH] refactor: scope dashboard records by organization Attach clients, rooms, services, bookings, contacts, addresses, invoices, and contracts to organizations. Filter dashboard loads and related option lists by the active organization from the session. Ensure create, update, and archive actions only affect records in the active organization. --- src/lib/server/db/addresses.schema.ts | 9 ++- src/lib/server/db/bookings.schema.ts | 5 ++ src/lib/server/db/clients.schema.ts | 4 ++ src/lib/server/db/contacts.schema.ts | 9 ++- src/lib/server/db/contracts.schema.ts | 9 ++- src/lib/server/db/invoices.schema.ts | 9 ++- src/lib/server/db/rooms.schema.ts | 4 ++ src/lib/server/db/services.schema.ts | 4 ++ src/routes/dashboard/+layout.svelte | 4 +- .../dashboard/addresses/+page.server.ts | 19 +++--- src/routes/dashboard/bookings/+page.server.ts | 21 ++++--- src/routes/dashboard/clients/+page.server.ts | 18 ++++-- .../dashboard/clients/[id]/+layout.server.ts | 62 +++++++++++++++---- .../clients/[id]/addresses/+page.server.ts | 44 ++++++++++--- .../clients/[id]/bookings/+page.server.ts | 48 ++++++++++---- .../clients/[id]/contacts/+page.server.ts | 44 ++++++++++--- .../clients/[id]/contracts/+page.server.ts | 44 ++++++++++--- .../clients/[id]/invoices/+page.server.ts | 44 ++++++++++--- src/routes/dashboard/contacts/+page.server.ts | 17 ++--- .../dashboard/contracts/+page.server.ts | 19 +++--- src/routes/dashboard/invoices/+page.server.ts | 17 ++--- src/routes/dashboard/rooms/+page.server.ts | 19 ++++-- src/routes/dashboard/services/+page.server.ts | 18 ++++-- 23 files changed, 369 insertions(+), 122 deletions(-) diff --git a/src/lib/server/db/addresses.schema.ts b/src/lib/server/db/addresses.schema.ts index 308954b..a097a10 100644 --- a/src/lib/server/db/addresses.schema.ts +++ b/src/lib/server/db/addresses.schema.ts @@ -1,5 +1,6 @@ import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { clients } from './clients.schema'; +import { organizations } from './organizations.schema'; import { timestamps } from './shared.schema'; export const addresses = sqliteTable( @@ -8,6 +9,9 @@ export const addresses = sqliteTable( id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), + organizationId: text('organization_id') + .notNull() + .references(() => organizations.id, { onDelete: 'restrict' }), clientId: text('client_id') .notNull() .references(() => clients.id, { onDelete: 'restrict' }), @@ -21,5 +25,8 @@ export const addresses = sqliteTable( isPrimary: integer('is_primary', { mode: 'boolean' }).notNull().default(false), ...timestamps }, - (table) => [index('addresses_client_id_idx').on(table.clientId)] + (table) => [ + index('addresses_organization_id_idx').on(table.organizationId), + index('addresses_client_id_idx').on(table.clientId) + ] ); diff --git a/src/lib/server/db/bookings.schema.ts b/src/lib/server/db/bookings.schema.ts index aa709b5..67baec5 100644 --- a/src/lib/server/db/bookings.schema.ts +++ b/src/lib/server/db/bookings.schema.ts @@ -1,5 +1,6 @@ import { index, sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { clients } from './clients.schema'; +import { organizations } from './organizations.schema'; import { rooms } from './rooms.schema'; import { services } from './services.schema'; import { timestamps } from './shared.schema'; @@ -10,6 +11,9 @@ export const bookings = sqliteTable( id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), + organizationId: text('organization_id') + .notNull() + .references(() => organizations.id, { onDelete: 'restrict' }), clientId: text('client_id') .notNull() .references(() => clients.id, { onDelete: 'restrict' }), @@ -24,6 +28,7 @@ export const bookings = sqliteTable( ...timestamps }, (table) => [ + index('bookings_organization_id_idx').on(table.organizationId), index('bookings_client_id_idx').on(table.clientId), index('bookings_room_id_idx').on(table.roomId), index('bookings_service_id_idx').on(table.serviceId) diff --git a/src/lib/server/db/clients.schema.ts b/src/lib/server/db/clients.schema.ts index c251055..1105ea6 100644 --- a/src/lib/server/db/clients.schema.ts +++ b/src/lib/server/db/clients.schema.ts @@ -1,10 +1,14 @@ import { text, sqliteTable } from 'drizzle-orm/sqlite-core'; +import { organizations } from './organizations.schema'; import { timestamps } from './shared.schema'; export const clients = sqliteTable('clients', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), + organizationId: text('organization_id') + .notNull() + .references(() => organizations.id, { onDelete: 'restrict' }), name: text('name').notNull(), type: text('type').notNull().default('business'), status: text('status').notNull().default('active'), diff --git a/src/lib/server/db/contacts.schema.ts b/src/lib/server/db/contacts.schema.ts index 0198b58..ce18e50 100644 --- a/src/lib/server/db/contacts.schema.ts +++ b/src/lib/server/db/contacts.schema.ts @@ -1,5 +1,6 @@ import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { clients } from './clients.schema'; +import { organizations } from './organizations.schema'; import { timestamps } from './shared.schema'; export const contacts = sqliteTable( @@ -8,6 +9,9 @@ export const contacts = sqliteTable( id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), + organizationId: text('organization_id') + .notNull() + .references(() => organizations.id, { onDelete: 'restrict' }), clientId: text('client_id') .notNull() .references(() => clients.id, { onDelete: 'restrict' }), @@ -19,5 +23,8 @@ export const contacts = sqliteTable( notes: text('notes'), ...timestamps }, - (table) => [index('contacts_client_id_idx').on(table.clientId)] + (table) => [ + index('contacts_organization_id_idx').on(table.organizationId), + index('contacts_client_id_idx').on(table.clientId) + ] ); diff --git a/src/lib/server/db/contracts.schema.ts b/src/lib/server/db/contracts.schema.ts index be75044..5708061 100644 --- a/src/lib/server/db/contracts.schema.ts +++ b/src/lib/server/db/contracts.schema.ts @@ -1,5 +1,6 @@ import { index, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { clients } from './clients.schema'; +import { organizations } from './organizations.schema'; import { timestamps } from './shared.schema'; export const contracts = sqliteTable( @@ -8,6 +9,9 @@ export const contracts = sqliteTable( id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), + organizationId: text('organization_id') + .notNull() + .references(() => organizations.id, { onDelete: 'restrict' }), clientId: text('client_id') .notNull() .references(() => clients.id, { onDelete: 'restrict' }), @@ -19,5 +23,8 @@ export const contracts = sqliteTable( notes: text('notes'), ...timestamps }, - (table) => [index('contracts_client_id_idx').on(table.clientId)] + (table) => [ + index('contracts_organization_id_idx').on(table.organizationId), + index('contracts_client_id_idx').on(table.clientId) + ] ); diff --git a/src/lib/server/db/invoices.schema.ts b/src/lib/server/db/invoices.schema.ts index 97056b2..d8ae2f4 100644 --- a/src/lib/server/db/invoices.schema.ts +++ b/src/lib/server/db/invoices.schema.ts @@ -1,5 +1,6 @@ import { index, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { clients } from './clients.schema'; +import { organizations } from './organizations.schema'; import { timestamps } from './shared.schema'; export const invoices = sqliteTable( @@ -8,6 +9,9 @@ export const invoices = sqliteTable( id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), + organizationId: text('organization_id') + .notNull() + .references(() => organizations.id, { onDelete: 'restrict' }), clientId: text('client_id') .notNull() .references(() => clients.id, { onDelete: 'restrict' }), @@ -21,5 +25,8 @@ export const invoices = sqliteTable( notes: text('notes'), ...timestamps }, - (table) => [index('invoices_client_id_idx').on(table.clientId)] + (table) => [ + index('invoices_organization_id_idx').on(table.organizationId), + index('invoices_client_id_idx').on(table.clientId) + ] ); diff --git a/src/lib/server/db/rooms.schema.ts b/src/lib/server/db/rooms.schema.ts index b5c119e..e02f396 100644 --- a/src/lib/server/db/rooms.schema.ts +++ b/src/lib/server/db/rooms.schema.ts @@ -1,10 +1,14 @@ import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { organizations } from './organizations.schema'; import { timestamps } from './shared.schema'; export const rooms = sqliteTable('rooms', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), + organizationId: text('organization_id') + .notNull() + .references(() => organizations.id, { onDelete: 'restrict' }), name: text('name').notNull(), type: text('type').notNull().default('meeting_room'), sqFt: integer('sq_ft'), diff --git a/src/lib/server/db/services.schema.ts b/src/lib/server/db/services.schema.ts index 55cad37..a65e80d 100644 --- a/src/lib/server/db/services.schema.ts +++ b/src/lib/server/db/services.schema.ts @@ -1,10 +1,14 @@ import { real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { organizations } from './organizations.schema'; import { timestamps } from './shared.schema'; export const services = sqliteTable('services', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), + organizationId: text('organization_id') + .notNull() + .references(() => organizations.id, { onDelete: 'restrict' }), name: text('name').notNull(), category: text('category'), unit: text('unit').notNull().default('each'), diff --git a/src/routes/dashboard/+layout.svelte b/src/routes/dashboard/+layout.svelte index f2a1caa..b10aa25 100644 --- a/src/routes/dashboard/+layout.svelte +++ b/src/routes/dashboard/+layout.svelte @@ -5,7 +5,7 @@ import { Separator } from '$lib/components/ui/separator/index.js'; import * as Sidebar from '$lib/components/ui/sidebar/index.js'; - const { children } = $props(); + const { data, children } = $props(); type BreadcrumbItem = { label: string; @@ -79,7 +79,7 @@ - +
diff --git a/src/routes/dashboard/addresses/+page.server.ts b/src/routes/dashboard/addresses/+page.server.ts index e0f44aa..c255305 100644 --- a/src/routes/dashboard/addresses/+page.server.ts +++ b/src/routes/dashboard/addresses/+page.server.ts @@ -1,16 +1,17 @@ -import { asc, eq, isNull } from 'drizzle-orm'; +import { and, asc, eq, isNull } from 'drizzle-orm'; import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { addresses, clients } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import type { Actions, PageServerLoad } from './$types'; -async function loadOptions() { +async function loadOptions(organizationId: string) { const clientRows = await db .select({ id: clients.id, name: clients.name }) .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)); return { @@ -18,22 +19,24 @@ async function loadOptions() { }; } -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(addresses) - .where(isNull(addresses.archivedAt)) + .where(and(eq(addresses.organizationId, activeOrganizationId), isNull(addresses.archivedAt))) .orderBy(asc(addresses.label)); return { records, - options: await loadOptions(), + options: await loadOptions(activeOrganizationId), archiveForm: await superValidate(zod4(archiveSchema), { id: 'addresses-archive' }) }; }; export const actions: Actions = { archive: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(archiveSchema), { id: 'addresses-archive' }); if (!form.valid) return message(form, 'Address id is required.', { status: 400 }); @@ -41,7 +44,9 @@ export const actions: Actions = { await db .update(addresses) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(eq(addresses.id, form.data.id)); + .where( + and(eq(addresses.id, form.data.id), eq(addresses.organizationId, activeOrganizationId)) + ); return message(form, 'Address archived.'); } diff --git a/src/routes/dashboard/bookings/+page.server.ts b/src/routes/dashboard/bookings/+page.server.ts index 836dfca..f00fd82 100644 --- a/src/routes/dashboard/bookings/+page.server.ts +++ b/src/routes/dashboard/bookings/+page.server.ts @@ -1,27 +1,28 @@ -import { asc, eq, isNull } from 'drizzle-orm'; +import { and, asc, eq, isNull } from 'drizzle-orm'; import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { bookings, clients, rooms, services } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import type { Actions, PageServerLoad } from './$types'; -async function loadOptions() { +async function loadOptions(organizationId: string) { const [clientRows, roomRows, serviceRows] = await Promise.all([ db .select({ id: clients.id, name: clients.name }) .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)), db .select({ id: rooms.id, name: rooms.name }) .from(rooms) - .where(isNull(rooms.archivedAt)) + .where(and(eq(rooms.organizationId, organizationId), isNull(rooms.archivedAt))) .orderBy(asc(rooms.name)), db .select({ id: services.id, name: services.name }) .from(services) - .where(isNull(services.archivedAt)) + .where(and(eq(services.organizationId, organizationId), isNull(services.archivedAt))) .orderBy(asc(services.name)) ]); @@ -35,22 +36,24 @@ async function loadOptions() { }; } -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(bookings) - .where(isNull(bookings.archivedAt)) + .where(and(eq(bookings.organizationId, activeOrganizationId), isNull(bookings.archivedAt))) .orderBy(asc(bookings.startsAt)); return { records, - options: await loadOptions(), + options: await loadOptions(activeOrganizationId), archiveForm: await superValidate(zod4(archiveSchema), { id: 'bookings-archive' }) }; }; export const actions: Actions = { archive: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(archiveSchema), { id: 'bookings-archive' }); if (!form.valid) return message(form, 'Booking id is required.', { status: 400 }); @@ -58,7 +61,7 @@ export const actions: Actions = { await db .update(bookings) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(eq(bookings.id, form.data.id)); + .where(and(eq(bookings.id, form.data.id), eq(bookings.organizationId, activeOrganizationId))); return message(form, 'Booking archived.'); } diff --git a/src/routes/dashboard/clients/+page.server.ts b/src/routes/dashboard/clients/+page.server.ts index 714f636..931b37f 100644 --- a/src/routes/dashboard/clients/+page.server.ts +++ b/src/routes/dashboard/clients/+page.server.ts @@ -1,17 +1,19 @@ -import { asc, eq, isNull } from 'drizzle-orm'; +import { and, asc, eq, isNull } from 'drizzle-orm'; import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { addresses, clients, contacts } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import { clientEditSchema, clientOnboardingCreateSchema } from '$lib/schemas/clients.schema'; import type { Actions, PageServerLoad } from './$types'; -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, activeOrganizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)); return { @@ -34,6 +36,7 @@ export const load: PageServerLoad = async () => { export const actions: Actions = { create: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(clientOnboardingCreateSchema), { id: 'clients-create' }); @@ -47,6 +50,7 @@ export const actions: Actions = { await tx.insert(clients).values({ id: clientId, + organizationId: activeOrganizationId, name: form.data.name, type: form.data.type, status: form.data.status, @@ -58,6 +62,7 @@ export const actions: Actions = { await tx.insert(contacts).values({ id: crypto.randomUUID(), + organizationId: activeOrganizationId, clientId, name: form.data.primaryContactName, role: form.data.primaryContactRole || null, @@ -71,6 +76,7 @@ export const actions: Actions = { await tx.insert(addresses).values({ id: crypto.randomUUID(), + organizationId: activeOrganizationId, clientId, label: form.data.primaryAddressLabel, line1: form.data.primaryAddressLine1, @@ -92,6 +98,7 @@ export const actions: Actions = { }, edit: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(clientEditSchema), { id: 'clients-edit' }); if (!form.valid) return message(form, 'Check the highlighted fields.', { status: 400 }); @@ -107,7 +114,7 @@ export const actions: Actions = { notes: form.data.notes || null, updatedAt: new Date() }) - .where(eq(clients.id, form.data.id)); + .where(and(eq(clients.id, form.data.id), eq(clients.organizationId, activeOrganizationId))); } catch { return message(form, 'Unable to update client.', { status: 400 }); } @@ -116,6 +123,7 @@ export const actions: Actions = { }, archive: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(archiveSchema), { id: 'clients-archive' }); if (!form.valid) return message(form, 'Client id is required.', { status: 400 }); @@ -123,7 +131,7 @@ export const actions: Actions = { await db .update(clients) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(eq(clients.id, form.data.id)); + .where(and(eq(clients.id, form.data.id), eq(clients.organizationId, activeOrganizationId))); return message(form, 'Client archived.'); } diff --git a/src/routes/dashboard/clients/[id]/+layout.server.ts b/src/routes/dashboard/clients/[id]/+layout.server.ts index fcc1f61..d17ecac 100644 --- a/src/routes/dashboard/clients/[id]/+layout.server.ts +++ b/src/routes/dashboard/clients/[id]/+layout.server.ts @@ -11,6 +11,7 @@ import { rooms, services } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { clientEditSchema } from '$lib/schemas/clients.schema'; import type { LayoutServerLoad } from './$types'; import { superValidate } from 'sveltekit-superforms/server'; @@ -30,22 +31,22 @@ function formValues(row: typeof clients.$inferSelect) { }; } -async function loadOptions() { +async function loadOptions(organizationId: string) { const [clientRows, roomRows, serviceRows] = await Promise.all([ db .select({ id: clients.id, name: clients.name }) .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)), db .select({ id: rooms.id, name: rooms.name }) .from(rooms) - .where(isNull(rooms.archivedAt)) + .where(and(eq(rooms.organizationId, organizationId), isNull(rooms.archivedAt))) .orderBy(asc(rooms.name)), db .select({ id: services.id, name: services.name }) .from(services) - .where(isNull(services.archivedAt)) + .where(and(eq(services.organizationId, organizationId), isNull(services.archivedAt))) .orderBy(asc(services.name)) ]); @@ -59,11 +60,18 @@ async function loadOptions() { }; } -export const load: LayoutServerLoad = async ({ params }) => { +export const load: LayoutServerLoad = async ({ locals, params }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const [client] = await db .select() .from(clients) - .where(and(eq(clients.id, params.id), isNull(clients.archivedAt))) + .where( + and( + eq(clients.id, params.id), + eq(clients.organizationId, activeOrganizationId), + isNull(clients.archivedAt) + ) + ) .limit(1); if (!client) error(404, 'Client not found'); @@ -73,29 +81,59 @@ export const load: LayoutServerLoad = async ({ params }) => { db .select() .from(addresses) - .where(and(eq(addresses.clientId, params.id), isNull(addresses.archivedAt))) + .where( + and( + eq(addresses.clientId, params.id), + eq(addresses.organizationId, activeOrganizationId), + isNull(addresses.archivedAt) + ) + ) .orderBy(asc(addresses.label)), db .select() .from(contacts) - .where(and(eq(contacts.clientId, params.id), isNull(contacts.archivedAt))) + .where( + and( + eq(contacts.clientId, params.id), + eq(contacts.organizationId, activeOrganizationId), + isNull(contacts.archivedAt) + ) + ) .orderBy(asc(contacts.name)), db .select() .from(invoices) - .where(and(eq(invoices.clientId, params.id), isNull(invoices.archivedAt))) + .where( + and( + eq(invoices.clientId, params.id), + eq(invoices.organizationId, activeOrganizationId), + isNull(invoices.archivedAt) + ) + ) .orderBy(asc(invoices.invoiceNumber)), db .select() .from(contracts) - .where(and(eq(contracts.clientId, params.id), isNull(contracts.archivedAt))) + .where( + and( + eq(contracts.clientId, params.id), + eq(contracts.organizationId, activeOrganizationId), + isNull(contracts.archivedAt) + ) + ) .orderBy(asc(contracts.title)), db .select() .from(bookings) - .where(and(eq(bookings.clientId, params.id), isNull(bookings.archivedAt))) + .where( + and( + eq(bookings.clientId, params.id), + eq(bookings.organizationId, activeOrganizationId), + isNull(bookings.archivedAt) + ) + ) .orderBy(asc(bookings.startsAt)), - loadOptions() + loadOptions(activeOrganizationId) ]); return { diff --git a/src/routes/dashboard/clients/[id]/addresses/+page.server.ts b/src/routes/dashboard/clients/[id]/addresses/+page.server.ts index fc7ef0a..d1d76b2 100644 --- a/src/routes/dashboard/clients/[id]/addresses/+page.server.ts +++ b/src/routes/dashboard/clients/[id]/addresses/+page.server.ts @@ -3,15 +3,16 @@ import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { addresses, clients } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import { addressCreateSchema, addressEditSchema } from '$lib/schemas/addresses.schema'; import type { Actions, PageServerLoad } from './$types'; -async function loadOptions() { +async function loadOptions(organizationId: string) { const clientRows = await db .select({ id: clients.id, name: clients.name }) .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)); return { @@ -19,11 +20,18 @@ async function loadOptions() { }; } -export const load: PageServerLoad = async ({ params }) => { +export const load: PageServerLoad = async ({ locals, params }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(addresses) - .where(and(eq(addresses.clientId, params.id), isNull(addresses.archivedAt))) + .where( + and( + eq(addresses.clientId, params.id), + eq(addresses.organizationId, activeOrganizationId), + isNull(addresses.archivedAt) + ) + ) .orderBy(asc(addresses.label)); return { @@ -42,7 +50,7 @@ export const load: PageServerLoad = async ({ params }) => { isPrimary: record.isPrimary ? 'true' : 'false' } })), - options: await loadOptions(), + options: await loadOptions(activeOrganizationId), createForm: await superValidate({ clientId: params.id }, zod4(addressCreateSchema), { id: 'addresses-create' }), @@ -52,7 +60,8 @@ export const load: PageServerLoad = async ({ params }) => { }; export const actions: Actions = { - create: async ({ params, request }) => { + create: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(addressCreateSchema), { @@ -64,6 +73,7 @@ export const actions: Actions = { try { await db.insert(addresses).values({ id: crypto.randomUUID(), + organizationId: activeOrganizationId, clientId: form.data.clientId, label: form.data.label, line1: form.data.line1, @@ -83,7 +93,8 @@ export const actions: Actions = { return message(form, 'Address created.'); }, - edit: async ({ params, request }) => { + edit: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(addressEditSchema), { id: 'addresses-edit' }); @@ -105,7 +116,13 @@ export const actions: Actions = { isPrimary: form.data.isPrimary === 'true', updatedAt: new Date() }) - .where(and(eq(addresses.id, form.data.id), eq(addresses.clientId, params.id))); + .where( + and( + eq(addresses.id, form.data.id), + eq(addresses.clientId, params.id), + eq(addresses.organizationId, activeOrganizationId) + ) + ); } catch { return message(form, 'Unable to update address.', { status: 400 }); } @@ -113,7 +130,8 @@ export const actions: Actions = { return message(form, 'Address updated.'); }, - archive: async ({ params, request }) => { + archive: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const form = await superValidate(await request.formData(), zod4(archiveSchema), { id: 'addresses-archive' }); @@ -123,7 +141,13 @@ export const actions: Actions = { await db .update(addresses) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(and(eq(addresses.id, form.data.id), eq(addresses.clientId, params.id))); + .where( + and( + eq(addresses.id, form.data.id), + eq(addresses.clientId, params.id), + eq(addresses.organizationId, activeOrganizationId) + ) + ); return message(form, 'Address archived.'); } diff --git a/src/routes/dashboard/clients/[id]/bookings/+page.server.ts b/src/routes/dashboard/clients/[id]/bookings/+page.server.ts index 3c7574a..095aba6 100644 --- a/src/routes/dashboard/clients/[id]/bookings/+page.server.ts +++ b/src/routes/dashboard/clients/[id]/bookings/+page.server.ts @@ -3,26 +3,27 @@ import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { bookings, clients, rooms, services } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import { bookingCreateSchema, bookingEditSchema } from '$lib/schemas/bookings.schema'; import type { Actions, PageServerLoad } from './$types'; -async function loadOptions() { +async function loadOptions(organizationId: string) { const [clientRows, roomRows, serviceRows] = await Promise.all([ db .select({ id: clients.id, name: clients.name }) .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)), db .select({ id: rooms.id, name: rooms.name }) .from(rooms) - .where(isNull(rooms.archivedAt)) + .where(and(eq(rooms.organizationId, organizationId), isNull(rooms.archivedAt))) .orderBy(asc(rooms.name)), db .select({ id: services.id, name: services.name }) .from(services) - .where(isNull(services.archivedAt)) + .where(and(eq(services.organizationId, organizationId), isNull(services.archivedAt))) .orderBy(asc(services.name)) ]); @@ -36,11 +37,18 @@ async function loadOptions() { }; } -export const load: PageServerLoad = async ({ params }) => { +export const load: PageServerLoad = async ({ locals, params }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(bookings) - .where(and(eq(bookings.clientId, params.id), isNull(bookings.archivedAt))) + .where( + and( + eq(bookings.clientId, params.id), + eq(bookings.organizationId, activeOrganizationId), + isNull(bookings.archivedAt) + ) + ) .orderBy(asc(bookings.startsAt)); return { @@ -57,7 +65,7 @@ export const load: PageServerLoad = async ({ params }) => { notes: record.notes ?? '' } })), - options: await loadOptions(), + options: await loadOptions(activeOrganizationId), createForm: await superValidate({ clientId: params.id }, zod4(bookingCreateSchema), { id: 'bookings-create' }), @@ -67,7 +75,8 @@ export const load: PageServerLoad = async ({ params }) => { }; export const actions: Actions = { - create: async ({ params, request }) => { + create: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(bookingCreateSchema), { @@ -79,6 +88,7 @@ export const actions: Actions = { try { await db.insert(bookings).values({ id: crypto.randomUUID(), + organizationId: activeOrganizationId, clientId: form.data.clientId, roomId: form.data.roomId, serviceId: form.data.serviceId || null, @@ -96,7 +106,8 @@ export const actions: Actions = { return message(form, 'Booking created.'); }, - edit: async ({ params, request }) => { + edit: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(bookingEditSchema), { @@ -118,7 +129,13 @@ export const actions: Actions = { notes: form.data.notes || null, updatedAt: new Date() }) - .where(and(eq(bookings.id, form.data.id), eq(bookings.clientId, params.id))); + .where( + and( + eq(bookings.id, form.data.id), + eq(bookings.clientId, params.id), + eq(bookings.organizationId, activeOrganizationId) + ) + ); } catch { return message(form, 'Unable to update booking.', { status: 400 }); } @@ -126,7 +143,8 @@ export const actions: Actions = { return message(form, 'Booking updated.'); }, - archive: async ({ params, request }) => { + archive: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const form = await superValidate(await request.formData(), zod4(archiveSchema), { id: 'bookings-archive' }); @@ -136,7 +154,13 @@ export const actions: Actions = { await db .update(bookings) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(and(eq(bookings.id, form.data.id), eq(bookings.clientId, params.id))); + .where( + and( + eq(bookings.id, form.data.id), + eq(bookings.clientId, params.id), + eq(bookings.organizationId, activeOrganizationId) + ) + ); return message(form, 'Booking archived.'); } diff --git a/src/routes/dashboard/clients/[id]/contacts/+page.server.ts b/src/routes/dashboard/clients/[id]/contacts/+page.server.ts index 03c4d83..86e0bd2 100644 --- a/src/routes/dashboard/clients/[id]/contacts/+page.server.ts +++ b/src/routes/dashboard/clients/[id]/contacts/+page.server.ts @@ -3,15 +3,16 @@ import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { contacts, clients } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import { contactCreateSchema, contactEditSchema } from '$lib/schemas/contacts.schema'; import type { Actions, PageServerLoad } from './$types'; -async function loadOptions() { +async function loadOptions(organizationId: string) { const clientRows = await db .select({ id: clients.id, name: clients.name }) .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)); return { @@ -19,11 +20,18 @@ async function loadOptions() { }; } -export const load: PageServerLoad = async ({ params }) => { +export const load: PageServerLoad = async ({ locals, params }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(contacts) - .where(and(eq(contacts.clientId, params.id), isNull(contacts.archivedAt))) + .where( + and( + eq(contacts.clientId, params.id), + eq(contacts.organizationId, activeOrganizationId), + isNull(contacts.archivedAt) + ) + ) .orderBy(asc(contacts.name)); return { @@ -40,7 +48,7 @@ export const load: PageServerLoad = async ({ params }) => { notes: record.notes ?? '' } })), - options: await loadOptions(), + options: await loadOptions(activeOrganizationId), createForm: await superValidate({ clientId: params.id }, zod4(contactCreateSchema), { id: 'contacts-create' }), @@ -50,7 +58,8 @@ export const load: PageServerLoad = async ({ params }) => { }; export const actions: Actions = { - create: async ({ params, request }) => { + create: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(contactCreateSchema), { @@ -62,6 +71,7 @@ export const actions: Actions = { try { await db.insert(contacts).values({ id: crypto.randomUUID(), + organizationId: activeOrganizationId, clientId: form.data.clientId, name: form.data.name, role: form.data.role || null, @@ -79,7 +89,8 @@ export const actions: Actions = { return message(form, 'Contact created.'); }, - edit: async ({ params, request }) => { + edit: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(contactEditSchema), { id: 'contacts-edit' }); @@ -99,7 +110,13 @@ export const actions: Actions = { notes: form.data.notes || null, updatedAt: new Date() }) - .where(and(eq(contacts.id, form.data.id), eq(contacts.clientId, params.id))); + .where( + and( + eq(contacts.id, form.data.id), + eq(contacts.clientId, params.id), + eq(contacts.organizationId, activeOrganizationId) + ) + ); } catch { return message(form, 'Unable to update contact.', { status: 400 }); } @@ -107,7 +124,8 @@ export const actions: Actions = { return message(form, 'Contact updated.'); }, - archive: async ({ params, request }) => { + archive: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const form = await superValidate(await request.formData(), zod4(archiveSchema), { id: 'contacts-archive' }); @@ -117,7 +135,13 @@ export const actions: Actions = { await db .update(contacts) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(and(eq(contacts.id, form.data.id), eq(contacts.clientId, params.id))); + .where( + and( + eq(contacts.id, form.data.id), + eq(contacts.clientId, params.id), + eq(contacts.organizationId, activeOrganizationId) + ) + ); return message(form, 'Contact archived.'); } diff --git a/src/routes/dashboard/clients/[id]/contracts/+page.server.ts b/src/routes/dashboard/clients/[id]/contracts/+page.server.ts index e9dd191..84ef8d6 100644 --- a/src/routes/dashboard/clients/[id]/contracts/+page.server.ts +++ b/src/routes/dashboard/clients/[id]/contracts/+page.server.ts @@ -3,15 +3,16 @@ import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { contracts, clients } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import { contractCreateSchema, contractEditSchema } from '$lib/schemas/contracts.schema'; import type { Actions, PageServerLoad } from './$types'; -async function loadOptions() { +async function loadOptions(organizationId: string) { const clientRows = await db .select({ id: clients.id, name: clients.name }) .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)); return { @@ -19,11 +20,18 @@ async function loadOptions() { }; } -export const load: PageServerLoad = async ({ params }) => { +export const load: PageServerLoad = async ({ locals, params }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(contracts) - .where(and(eq(contracts.clientId, params.id), isNull(contracts.archivedAt))) + .where( + and( + eq(contracts.clientId, params.id), + eq(contracts.organizationId, activeOrganizationId), + isNull(contracts.archivedAt) + ) + ) .orderBy(asc(contracts.title)); return { @@ -40,7 +48,7 @@ export const load: PageServerLoad = async ({ params }) => { notes: record.notes ?? '' } })), - options: await loadOptions(), + options: await loadOptions(activeOrganizationId), createForm: await superValidate({ clientId: params.id }, zod4(contractCreateSchema), { id: 'contracts-create' }), @@ -50,7 +58,8 @@ export const load: PageServerLoad = async ({ params }) => { }; export const actions: Actions = { - create: async ({ params, request }) => { + create: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(contractCreateSchema), { @@ -62,6 +71,7 @@ export const actions: Actions = { try { await db.insert(contracts).values({ id: crypto.randomUUID(), + organizationId: activeOrganizationId, clientId: form.data.clientId, title: form.data.title, startDate: form.data.startDate, @@ -79,7 +89,8 @@ export const actions: Actions = { return message(form, 'Contract created.'); }, - edit: async ({ params, request }) => { + edit: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(contractEditSchema), { @@ -101,7 +112,13 @@ export const actions: Actions = { notes: form.data.notes || null, updatedAt: new Date() }) - .where(and(eq(contracts.id, form.data.id), eq(contracts.clientId, params.id))); + .where( + and( + eq(contracts.id, form.data.id), + eq(contracts.clientId, params.id), + eq(contracts.organizationId, activeOrganizationId) + ) + ); } catch { return message(form, 'Unable to update contract.', { status: 400 }); } @@ -109,7 +126,8 @@ export const actions: Actions = { return message(form, 'Contract updated.'); }, - archive: async ({ params, request }) => { + archive: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const form = await superValidate(await request.formData(), zod4(archiveSchema), { id: 'contracts-archive' }); @@ -119,7 +137,13 @@ export const actions: Actions = { await db .update(contracts) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(and(eq(contracts.id, form.data.id), eq(contracts.clientId, params.id))); + .where( + and( + eq(contracts.id, form.data.id), + eq(contracts.clientId, params.id), + eq(contracts.organizationId, activeOrganizationId) + ) + ); return message(form, 'Contract archived.'); } diff --git a/src/routes/dashboard/clients/[id]/invoices/+page.server.ts b/src/routes/dashboard/clients/[id]/invoices/+page.server.ts index ce9c5cb..964c3c8 100644 --- a/src/routes/dashboard/clients/[id]/invoices/+page.server.ts +++ b/src/routes/dashboard/clients/[id]/invoices/+page.server.ts @@ -3,15 +3,16 @@ import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { invoices, clients } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import { invoiceCreateSchema, invoiceEditSchema } from '$lib/schemas/invoices.schema'; import type { Actions, PageServerLoad } from './$types'; -async function loadOptions() { +async function loadOptions(organizationId: string) { const clientRows = await db .select({ id: clients.id, name: clients.name }) .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)); return { @@ -19,11 +20,18 @@ async function loadOptions() { }; } -export const load: PageServerLoad = async ({ params }) => { +export const load: PageServerLoad = async ({ locals, params }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(invoices) - .where(and(eq(invoices.clientId, params.id), isNull(invoices.archivedAt))) + .where( + and( + eq(invoices.clientId, params.id), + eq(invoices.organizationId, activeOrganizationId), + isNull(invoices.archivedAt) + ) + ) .orderBy(asc(invoices.invoiceNumber)); return { @@ -42,7 +50,7 @@ export const load: PageServerLoad = async ({ params }) => { notes: record.notes ?? '' } })), - options: await loadOptions(), + options: await loadOptions(activeOrganizationId), createForm: await superValidate({ clientId: params.id }, zod4(invoiceCreateSchema), { id: 'invoices-create' }), @@ -52,7 +60,8 @@ export const load: PageServerLoad = async ({ params }) => { }; export const actions: Actions = { - create: async ({ params, request }) => { + create: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(invoiceCreateSchema), { @@ -64,6 +73,7 @@ export const actions: Actions = { try { await db.insert(invoices).values({ id: crypto.randomUUID(), + organizationId: activeOrganizationId, clientId: form.data.clientId, invoiceNumber: form.data.invoiceNumber, issueDate: form.data.issueDate, @@ -83,7 +93,8 @@ export const actions: Actions = { return message(form, 'Invoice created.'); }, - edit: async ({ params, request }) => { + edit: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(invoiceEditSchema), { id: 'invoices-edit' }); @@ -105,7 +116,13 @@ export const actions: Actions = { notes: form.data.notes || null, updatedAt: new Date() }) - .where(and(eq(invoices.id, form.data.id), eq(invoices.clientId, params.id))); + .where( + and( + eq(invoices.id, form.data.id), + eq(invoices.clientId, params.id), + eq(invoices.organizationId, activeOrganizationId) + ) + ); } catch { return message(form, 'Unable to update invoice.', { status: 400 }); } @@ -113,7 +130,8 @@ export const actions: Actions = { return message(form, 'Invoice updated.'); }, - archive: async ({ params, request }) => { + archive: async ({ locals, params, request }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const form = await superValidate(await request.formData(), zod4(archiveSchema), { id: 'invoices-archive' }); @@ -123,7 +141,13 @@ export const actions: Actions = { await db .update(invoices) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(and(eq(invoices.id, form.data.id), eq(invoices.clientId, params.id))); + .where( + and( + eq(invoices.id, form.data.id), + eq(invoices.clientId, params.id), + eq(invoices.organizationId, activeOrganizationId) + ) + ); return message(form, 'Invoice archived.'); } diff --git a/src/routes/dashboard/contacts/+page.server.ts b/src/routes/dashboard/contacts/+page.server.ts index 6aa04c6..f5a3e36 100644 --- a/src/routes/dashboard/contacts/+page.server.ts +++ b/src/routes/dashboard/contacts/+page.server.ts @@ -1,16 +1,17 @@ -import { asc, eq, isNull } from 'drizzle-orm'; +import { and, asc, eq, isNull } from 'drizzle-orm'; import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { contacts, clients } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import type { Actions, PageServerLoad } from './$types'; -async function loadOptions() { +async function loadOptions(organizationId: string) { const clientRows = await db .select({ id: clients.id, name: clients.name }) .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)); return { @@ -18,22 +19,24 @@ async function loadOptions() { }; } -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(contacts) - .where(isNull(contacts.archivedAt)) + .where(and(eq(contacts.organizationId, activeOrganizationId), isNull(contacts.archivedAt))) .orderBy(asc(contacts.name)); return { records, - options: await loadOptions(), + options: await loadOptions(activeOrganizationId), archiveForm: await superValidate(zod4(archiveSchema), { id: 'contacts-archive' }) }; }; export const actions: Actions = { archive: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(archiveSchema), { id: 'contacts-archive' }); if (!form.valid) return message(form, 'Contact id is required.', { status: 400 }); @@ -41,7 +44,7 @@ export const actions: Actions = { await db .update(contacts) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(eq(contacts.id, form.data.id)); + .where(and(eq(contacts.id, form.data.id), eq(contacts.organizationId, activeOrganizationId))); return message(form, 'Contact archived.'); } diff --git a/src/routes/dashboard/contracts/+page.server.ts b/src/routes/dashboard/contracts/+page.server.ts index 2d45db9..cca177b 100644 --- a/src/routes/dashboard/contracts/+page.server.ts +++ b/src/routes/dashboard/contracts/+page.server.ts @@ -1,16 +1,17 @@ -import { asc, eq, isNull } from 'drizzle-orm'; +import { and, asc, eq, isNull } from 'drizzle-orm'; import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { contracts, clients } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import type { Actions, PageServerLoad } from './$types'; -async function loadOptions() { +async function loadOptions(organizationId: string) { const clientRows = await db .select({ id: clients.id, name: clients.name }) .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)); return { @@ -18,22 +19,24 @@ async function loadOptions() { }; } -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(contracts) - .where(isNull(contracts.archivedAt)) + .where(and(eq(contracts.organizationId, activeOrganizationId), isNull(contracts.archivedAt))) .orderBy(asc(contracts.title)); return { records, - options: await loadOptions(), + options: await loadOptions(activeOrganizationId), archiveForm: await superValidate(zod4(archiveSchema), { id: 'contracts-archive' }) }; }; export const actions: Actions = { archive: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(archiveSchema), { id: 'contracts-archive' }); if (!form.valid) return message(form, 'Contract id is required.', { status: 400 }); @@ -41,7 +44,9 @@ export const actions: Actions = { await db .update(contracts) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(eq(contracts.id, form.data.id)); + .where( + and(eq(contracts.id, form.data.id), eq(contracts.organizationId, activeOrganizationId)) + ); return message(form, 'Contract archived.'); } diff --git a/src/routes/dashboard/invoices/+page.server.ts b/src/routes/dashboard/invoices/+page.server.ts index 4e7b793..5a9f44c 100644 --- a/src/routes/dashboard/invoices/+page.server.ts +++ b/src/routes/dashboard/invoices/+page.server.ts @@ -1,16 +1,17 @@ -import { asc, eq, isNull } from 'drizzle-orm'; +import { and, asc, eq, isNull } from 'drizzle-orm'; import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { invoices, clients } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import type { Actions, PageServerLoad } from './$types'; -async function loadOptions() { +async function loadOptions(organizationId: string) { const clientRows = await db .select({ id: clients.id, name: clients.name }) .from(clients) - .where(isNull(clients.archivedAt)) + .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)); return { @@ -18,22 +19,24 @@ async function loadOptions() { }; } -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(invoices) - .where(isNull(invoices.archivedAt)) + .where(and(eq(invoices.organizationId, activeOrganizationId), isNull(invoices.archivedAt))) .orderBy(asc(invoices.invoiceNumber)); return { records, - options: await loadOptions(), + options: await loadOptions(activeOrganizationId), archiveForm: await superValidate(zod4(archiveSchema), { id: 'invoices-archive' }) }; }; export const actions: Actions = { archive: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(archiveSchema), { id: 'invoices-archive' }); if (!form.valid) return message(form, 'Invoice id is required.', { status: 400 }); @@ -41,7 +44,7 @@ export const actions: Actions = { await db .update(invoices) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(eq(invoices.id, form.data.id)); + .where(and(eq(invoices.id, form.data.id), eq(invoices.organizationId, activeOrganizationId))); return message(form, 'Invoice archived.'); } diff --git a/src/routes/dashboard/rooms/+page.server.ts b/src/routes/dashboard/rooms/+page.server.ts index 51c8397..c874002 100644 --- a/src/routes/dashboard/rooms/+page.server.ts +++ b/src/routes/dashboard/rooms/+page.server.ts @@ -1,17 +1,19 @@ -import { asc, eq, isNull } from 'drizzle-orm'; +import { and, asc, eq, isNull } from 'drizzle-orm'; import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { rooms } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import { roomCreateSchema, roomEditSchema } from '$lib/schemas/rooms.schema'; import type { Actions, PageServerLoad } from './$types'; -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(rooms) - .where(isNull(rooms.archivedAt)) + .where(and(eq(rooms.organizationId, activeOrganizationId), isNull(rooms.archivedAt))) .orderBy(asc(rooms.name)); return { @@ -39,11 +41,13 @@ export const load: PageServerLoad = async () => { export const actions: Actions = { create: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(roomCreateSchema), { id: 'rooms-create' }); if (!form.valid) return message(form, 'Check the highlighted fields.', { status: 400 }); const sharedValues = { + organizationId: activeOrganizationId, name: form.data.name, type: form.data.type, sqFt: null, @@ -96,6 +100,7 @@ export const actions: Actions = { }, edit: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(roomEditSchema), { id: 'rooms-edit' }); if (!form.valid) return message(form, 'Check the highlighted fields.', { status: 400 }); @@ -140,7 +145,10 @@ export const actions: Actions = { }; try { - await db.update(rooms).set(roomValues).where(eq(rooms.id, form.data.id)); + await db + .update(rooms) + .set(roomValues) + .where(and(eq(rooms.id, form.data.id), eq(rooms.organizationId, activeOrganizationId))); } catch { return message(form, 'Unable to update room.', { status: 400 }); } @@ -149,6 +157,7 @@ export const actions: Actions = { }, archive: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(archiveSchema), { id: 'rooms-archive' }); if (!form.valid) return message(form, 'Room id is required.', { status: 400 }); @@ -156,7 +165,7 @@ export const actions: Actions = { await db .update(rooms) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(eq(rooms.id, form.data.id)); + .where(and(eq(rooms.id, form.data.id), eq(rooms.organizationId, activeOrganizationId))); return message(form, 'Room archived.'); } diff --git a/src/routes/dashboard/services/+page.server.ts b/src/routes/dashboard/services/+page.server.ts index 65d67b0..62d28f2 100644 --- a/src/routes/dashboard/services/+page.server.ts +++ b/src/routes/dashboard/services/+page.server.ts @@ -1,17 +1,19 @@ -import { asc, eq, isNull } from 'drizzle-orm'; +import { and, asc, eq, isNull } from 'drizzle-orm'; import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { services } from '$lib/server/db/schema'; +import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import { serviceCreateSchema, serviceEditSchema } from '$lib/schemas/services.schema'; import type { Actions, PageServerLoad } from './$types'; -export const load: PageServerLoad = async () => { +export const load: PageServerLoad = async ({ locals }) => { + const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(services) - .where(isNull(services.archivedAt)) + .where(and(eq(services.organizationId, activeOrganizationId), isNull(services.archivedAt))) .orderBy(asc(services.name)); return { @@ -34,6 +36,7 @@ export const load: PageServerLoad = async () => { export const actions: Actions = { create: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(serviceCreateSchema), { id: 'services-create' }); if (!form.valid) return message(form, 'Check the highlighted fields.', { status: 400 }); @@ -41,6 +44,7 @@ export const actions: Actions = { try { await db.insert(services).values({ id: crypto.randomUUID(), + organizationId: activeOrganizationId, name: form.data.name, category: form.data.category || null, unit: form.data.unit, @@ -57,6 +61,7 @@ export const actions: Actions = { }, edit: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(serviceEditSchema), { id: 'services-edit' }); if (!form.valid) return message(form, 'Check the highlighted fields.', { status: 400 }); @@ -72,7 +77,9 @@ export const actions: Actions = { description: form.data.description || null, updatedAt: new Date() }) - .where(eq(services.id, form.data.id)); + .where( + and(eq(services.id, form.data.id), eq(services.organizationId, activeOrganizationId)) + ); } catch { return message(form, 'Unable to update service.', { status: 400 }); } @@ -81,6 +88,7 @@ export const actions: Actions = { }, archive: async (event) => { + const { activeOrganizationId } = await loadOrganizationContext(event.locals); const form = await superValidate(event, zod4(archiveSchema), { id: 'services-archive' }); if (!form.valid) return message(form, 'Service id is required.', { status: 400 }); @@ -88,7 +96,7 @@ export const actions: Actions = { await db .update(services) .set({ archivedAt: new Date(), updatedAt: new Date() }) - .where(eq(services.id, form.data.id)); + .where(and(eq(services.id, form.data.id), eq(services.organizationId, activeOrganizationId))); return message(form, 'Service archived.'); }