Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions drizzle/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,13 @@ async function seedBookings() {
const agencyUser = agencyUsers[i % agencyUsers.length];

if (!agencyUser) continue;
const userOrg = await db
.select()
.from(member)
.where(eq(member.userId, agencyUser.id))
.limit(1)
.then((r) => r[0]);
if (!userOrg) continue; // User somehow does not belong to an org
Comment thread
coderabbitai[bot] marked this conversation as resolved.

bookingData.push({
title: `${purposes[i % purposes.length]} - ${passengerNames[i % passengerNames.length]}`,
Expand All @@ -350,7 +357,7 @@ async function seedBookings() {
phoneNumber: `+1 (403) ${Math.floor(Math.random() * 900 + 100)}-${Math.floor(Math.random() * 9000 + 1000)}`,
surveyCompleted: status === BookingStatus.COMPLETED ? Math.random() > 0.5 : false,
status,
agencyId: agencyUser.id,
agencyId: userOrg.organizationId,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
driverId: driver.id,
Expand All @@ -373,6 +380,13 @@ async function seedBookings() {
const agencyUser = agencyUsers[(pastCount + i) % agencyUsers.length];

if (!agencyUser) continue;
const userOrg = await db
.select()
.from(member)
.where(eq(member.userId, agencyUser.id))
.limit(1)
.then((r) => r[0]);
if (!userOrg) continue; // User somehow does not belong to an org

bookingData.push({
title: `${purposes[(pastCount + i) % purposes.length]} - ${passengerNames[(pastCount + i) % passengerNames.length]}`,
Expand All @@ -383,7 +397,7 @@ async function seedBookings() {
phoneNumber: `+1 (403) ${Math.floor(Math.random() * 900 + 100)}-${Math.floor(Math.random() * 9000 + 1000)}`,
surveyCompleted: false,
status,
agencyId: agencyUser.id,
agencyId: userOrg.organizationId,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
driverId: driver.id,
Expand All @@ -404,6 +418,13 @@ async function seedBookings() {
const agencyUser = agencyUsers[(pastCount + todayCount + i) % agencyUsers.length];

if (!agencyUser) continue;
const userOrg = await db
.select()
.from(member)
.where(eq(member.userId, agencyUser.id))
.limit(1)
.then((r) => r[0]);
if (!userOrg) continue; // User somehow does not belong to an org

bookingData.push({
title: `${purposes[(pastCount + todayCount + i) % purposes.length]} - ${passengerNames[(pastCount + todayCount + i) % passengerNames.length]}`,
Expand All @@ -415,7 +436,7 @@ async function seedBookings() {
phoneNumber: `+1 (403) ${Math.floor(Math.random() * 900 + 100)}-${Math.floor(Math.random() * 9000 + 1000)}`,
surveyCompleted: false,
status: "incomplete" as BookingStatus,
agencyId: agencyUser.id,
agencyId: userOrg.organizationId,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
driverId: driver.id,
Expand Down
8 changes: 4 additions & 4 deletions src/app/debug/bookings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function formatTimeSlot(startTime: string, endTime: string): string {
return `${start} – ${end}`;
}

/** Example pre-filled booking for testing. agencyId must be a valid user.id; set dynamically from getCurrentUser. */
/** Example pre-filled booking for testing. agencyId must be a valid organization.id; set dynamically from getCurrentUser. */
const EXAMPLE_BOOKING = {
title: "Test Example",
pickupAddress: "The Inn from the Cold, 110 11 Ave SE, Calgary, AB",
Expand Down Expand Up @@ -150,7 +150,7 @@ export default function BookingDebugPage() {
phoneNumber: EXAMPLE_BOOKING.phoneNumber ?? "",
start: EXAMPLE_BOOKING.start, // will be kept in sync with picker (string)
end: "", // will be kept in sync with picker (string), auto-calculated
agencyId: "", // set from getCurrentUser (must be valid user.id for FK)
agencyId: "", // set from getCurrentUser (must be valid organization.id for FK)
purpose: EXAMPLE_BOOKING.purpose,
driverId: "",
status: BookingStatus.INCOMPLETE,
Expand Down Expand Up @@ -180,10 +180,10 @@ export default function BookingDebugPage() {
const listDriversQuery = api.bookings.listDrivers.useQuery();
const currentUserQuery = api.bookings.getCurrentUser.useQuery();

// Set agencyId from current user so it references a valid user (fixes FK constraint)
// Set agencyId from current user so it references a valid organization (fixes FK constraint)
useEffect(() => {
if (currentUserQuery.data && !form.values.agencyId) {
form.setFieldValue("agencyId", currentUserQuery.data.id);
form.setFieldValue("agencyId", currentUserQuery.data.agencyId);
}
}, [currentUserQuery.data, form.setFieldValue, form.values.agencyId]);

Expand Down
38 changes: 32 additions & 6 deletions src/server/api/routers/bookings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,19 @@ type DbContext = { db: typeof db };

/**
* Throws if the session user is not admin and not the booking's agency.
* @param session - Session with user id and role
* @param session - Session with user id, role and affiliated agency
* @param agencyId - Booking's agency id
* @throws TRPCError FORBIDDEN when not allowed
*/
function assertCanAccessBooking(
session: { user: { id: string; role?: string | null } },
session: {
user: { id: string; role?: string | null };
session: { activeOrganizationId?: string | null | undefined };
},
agencyId: string,
): void {
const role = session.user.role ?? "user";
const allowed = role === "admin" || agencyId === session.user.id;
const allowed = role === "admin" || agencyId === session.session.activeOrganizationId;
if (!allowed) {
throw new TRPCError({
code: "FORBIDDEN",
Expand Down Expand Up @@ -243,9 +246,16 @@ async function validateDriverForSlot(
export const bookingsRouter = createTRPCRouter({
/** Returns the current user id and role for the debug form default agencyId. */
getCurrentUser: protectedProcedure.query(async ({ ctx }) => {
if (!ctx.session.session.activeOrganizationId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "No active organization ID set",
});
}
return {
id: ctx.session.user.id,
role: ctx.session.user.role ?? "user",
agencyId: ctx.session.session.activeOrganizationId,
};
}),

Expand Down Expand Up @@ -397,8 +407,15 @@ export const bookingsRouter = createTRPCRouter({
const userId = ctx.session.user.id;
const role = ctx.session.user.role ?? "user";

// Only allow admins to specify agencyId; non-admins use their own ID
const agencyId = role === "admin" ? input.agencyId : userId;
if (!ctx.session.session.activeOrganizationId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "No active organization ID set",
});
}

// Only allow admins to specify agencyId; non-admins use their own agency ID
const agencyId = role === "admin" ? input.agencyId : ctx.session.session.activeOrganizationId;

const bookingData: BookingInsertType = {
title: input.title,
Expand Down Expand Up @@ -480,6 +497,15 @@ export const bookingsRouter = createTRPCRouter({
const startDate = input?.startDate ?? "1970-01-01T00:00:00-07:00";
let endDate = input?.endDate ?? "";

if (!ctx.session.session.activeOrganizationId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "No active organization ID set",
});
}

const agencyId = ctx.session.session.activeOrganizationId;

if (input === undefined || input.endDate === undefined) {
// No end date given; use explicit format so result matches isoTimeRegexFourDigitYears (-07:00)
endDate = dayjs()
Expand Down Expand Up @@ -527,7 +553,7 @@ export const bookingsRouter = createTRPCRouter({
and(
or(
eq(bookings.createdBy, userId),
eq(bookings.agencyId, userId),
eq(bookings.agencyId, agencyId),
eq(bookings.driverId, userId),
),
...conditions,
Expand Down
9 changes: 8 additions & 1 deletion src/server/api/routers/trip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ export const tripRouter = createTRPCRouter({
}),
)
.mutation(async ({ ctx, input }) => {
if (!ctx.session.session.activeOrganizationId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "No active organization ID set",
});
}

const [inserted] = await ctx.db
.insert(bookings)
.values({
Expand All @@ -32,7 +39,7 @@ export const tripRouter = createTRPCRouter({
destinationAddress: input.destinationAddress,
passengerInfo: `${input.residentName}|${input.phoneNumber}|${input.additionalInfo || ""}`,
phoneNumber: input.phoneNumber,
agencyId: ctx.session.user.id,
agencyId: ctx.session.session.activeOrganizationId,
purpose: input.purpose,
createdBy: ctx.session.user.id,
startTime: input.startTime,
Expand Down
2 changes: 1 addition & 1 deletion src/server/db/auth-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ export const invitation = pgTable("invitation", {

export const userRelations = relations(user, ({ many }) => ({
driverBookings: many(bookings, { relationName: "driverBookings" }),
agencyBookings: many(bookings, { relationName: "agencyBookings" }),
memberships: many(member),
}));

export const organizationRelations = relations(organization, ({ many }) => ({
members: many(member),
agencyBookings: many(bookings, { relationName: "agencyBookings" }),
}));

export const memberRelations = relations(member, ({ one, many }) => ({
Expand Down
8 changes: 4 additions & 4 deletions src/server/db/booking-schema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type InferInsertModel, type InferSelectModel, relations } from "drizzle-orm";
import { boolean, index, pgTable, serial, text, timestamp, varchar } from "drizzle-orm/pg-core";
import { BOOKING_STATUSES, BookingStatus } from "@/types/types";
import { user } from "./auth-schema";
import { organization, user } from "./auth-schema";

export const bookings = pgTable(
"bookings",
Expand All @@ -18,7 +18,7 @@ export const bookings = pgTable(
// the agency that created the booking
agencyId: text("agency_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
.references(() => organization.id, { onDelete: "cascade" }),
startTime: timestamp("start_time", {
mode: "string",
withTimezone: true,
Expand Down Expand Up @@ -51,9 +51,9 @@ export const bookings = pgTable(
);

export const bookingsRelations = relations(bookings, ({ one }) => ({
agency: one(user, {
agency: one(organization, {
fields: [bookings.agencyId],
references: [user.id],
references: [organization.id],
relationName: "agencyBookings",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}),
driver: one(user, {
Expand Down
Loading