Add account hierarchy framework with migrations, RLS policies, and UI components

This commit is contained in:
T. Zehetbauer
2026-03-31 22:18:04 +02:00
parent 7e7da0b465
commit 59546ad6d2
262 changed files with 11671 additions and 3927 deletions

View File

@@ -1,6 +1,7 @@
import type { Database } from '@kit/supabase/database';
import type { SupabaseClient } from '@supabase/supabase-js';
import type { Database } from '@kit/supabase/database';
import type { CreateBookingInput } from '../schema/booking.schema';
/* eslint-disable @typescript-eslint/no-explicit-any */
@@ -11,21 +12,31 @@ export function createBookingManagementApi(client: SupabaseClient<Database>) {
return {
// --- Rooms ---
async listRooms(accountId: string) {
const { data, error } = await client.from('rooms').select('*')
.eq('account_id', accountId).eq('is_active', true).order('room_number');
const { data, error } = await client
.from('rooms')
.select('*')
.eq('account_id', accountId)
.eq('is_active', true)
.order('room_number');
if (error) throw error;
return data ?? [];
},
async getRoom(roomId: string) {
const { data, error } = await client.from('rooms').select('*').eq('id', roomId).single();
const { data, error } = await client
.from('rooms')
.select('*')
.eq('id', roomId)
.single();
if (error) throw error;
return data;
},
// --- Availability ---
async checkAvailability(roomId: string, checkIn: string, checkOut: string) {
const { count, error } = await client.from('bookings').select('*', { count: 'exact', head: true })
const { count, error } = await client
.from('bookings')
.select('*', { count: 'exact', head: true })
.eq('room_id', roomId)
.not('status', 'in', '("cancelled","no_show")')
.lt('check_in', checkOut)
@@ -35,9 +46,15 @@ export function createBookingManagementApi(client: SupabaseClient<Database>) {
},
// --- Bookings ---
async listBookings(accountId: string, opts?: { status?: string; from?: string; to?: string; page?: number }) {
let query = client.from('bookings').select('*', { count: 'exact' })
.eq('account_id', accountId).order('check_in', { ascending: false });
async listBookings(
accountId: string,
opts?: { status?: string; from?: string; to?: string; page?: number },
) {
let query = client
.from('bookings')
.select('*', { count: 'exact' })
.eq('account_id', accountId)
.order('check_in', { ascending: false });
if (opts?.status) query = query.eq('status', opts.status);
if (opts?.from) query = query.gte('check_in', opts.from);
if (opts?.to) query = query.lte('check_out', opts.to);
@@ -49,48 +66,106 @@ export function createBookingManagementApi(client: SupabaseClient<Database>) {
},
async createBooking(input: CreateBookingInput) {
const available = await this.checkAvailability(input.roomId, input.checkIn, input.checkOut);
if (!available) throw new Error('Room is not available for the selected dates');
const available = await this.checkAvailability(
input.roomId,
input.checkIn,
input.checkOut,
);
if (!available)
throw new Error('Room is not available for the selected dates');
const { data, error } = await client.from('bookings').insert({
account_id: input.accountId, room_id: input.roomId, guest_id: input.guestId,
check_in: input.checkIn, check_out: input.checkOut,
adults: input.adults, children: input.children,
status: input.status, total_price: input.totalPrice, notes: input.notes,
}).select().single();
const { data, error } = await client
.from('bookings')
.insert({
account_id: input.accountId,
room_id: input.roomId,
guest_id: input.guestId,
check_in: input.checkIn,
check_out: input.checkOut,
adults: input.adults,
children: input.children,
status: input.status,
total_price: input.totalPrice,
notes: input.notes,
})
.select()
.single();
if (error) throw error;
return data;
},
async updateBookingStatus(bookingId: string, status: string) {
const { error } = await client.from('bookings').update({ status }).eq('id', bookingId);
const { error } = await client
.from('bookings')
.update({ status })
.eq('id', bookingId);
if (error) throw error;
},
// --- Guests ---
async listGuests(accountId: string, search?: string) {
let query = client.from('guests').select('*').eq('account_id', accountId).order('last_name');
if (search) query = query.or(`last_name.ilike.%${search}%,first_name.ilike.%${search}%,email.ilike.%${search}%`);
let query = client
.from('guests')
.select('*')
.eq('account_id', accountId)
.order('last_name');
if (search)
query = query.or(
`last_name.ilike.%${search}%,first_name.ilike.%${search}%,email.ilike.%${search}%`,
);
const { data, error } = await query;
if (error) throw error;
return data ?? [];
},
async createGuest(input: { accountId: string; firstName: string; lastName: string; email?: string; phone?: string; city?: string }) {
const { data, error } = await client.from('guests').insert({
account_id: input.accountId, first_name: input.firstName, last_name: input.lastName,
email: input.email, phone: input.phone, city: input.city,
}).select().single();
async createGuest(input: {
accountId: string;
firstName: string;
lastName: string;
email?: string;
phone?: string;
city?: string;
}) {
const { data, error } = await client
.from('guests')
.insert({
account_id: input.accountId,
first_name: input.firstName,
last_name: input.lastName,
email: input.email,
phone: input.phone,
city: input.city,
})
.select()
.single();
if (error) throw error;
return data;
},
async createRoom(input: { accountId: string; roomNumber: string; name?: string; roomType?: string; capacity?: number; floor?: number; pricePerNight: number; description?: string }) {
const { data, error } = await client.from('rooms').insert({
account_id: input.accountId, room_number: input.roomNumber, name: input.name,
room_type: input.roomType ?? 'standard', capacity: input.capacity ?? 2,
floor: input.floor, price_per_night: input.pricePerNight, description: input.description,
}).select().single();
async createRoom(input: {
accountId: string;
roomNumber: string;
name?: string;
roomType?: string;
capacity?: number;
floor?: number;
pricePerNight: number;
description?: string;
}) {
const { data, error } = await client
.from('rooms')
.insert({
account_id: input.accountId,
room_number: input.roomNumber,
name: input.name,
room_type: input.roomType ?? 'standard',
capacity: input.capacity ?? 2,
floor: input.floor,
price_per_night: input.pricePerNight,
description: input.description,
})
.select()
.single();
if (error) throw error;
return data;
},