diff --git a/src/app/dashboard/reservations/[id]/actions.ts b/src/app/dashboard/reservations/[id]/actions.ts index 37feeb7..054b32a 100644 --- a/src/app/dashboard/reservations/[id]/actions.ts +++ b/src/app/dashboard/reservations/[id]/actions.ts @@ -3,6 +3,8 @@ import { createClient } from "@/lib/supabase/server" import { revalidatePath } from "next/cache" +import { logAction } from "@/lib/logger" + export async function addPayment(reservationId: string, formData: FormData) { const supabase = await createClient() @@ -10,17 +12,23 @@ export async function addPayment(reservationId: string, formData: FormData) { const type = formData.get('type') const method = formData.get('method') - const { error } = await supabase.from('payments').insert({ + const { data, error } = await supabase.from('payments').insert({ reservation_id: reservationId, amount: amount, payment_type: type, payment_method: method, status: 'paid', // Assuming immediate payment for now paid_at: new Date().toISOString(), - }) + }).select().single() if (error) throw new Error(error.message) + await logAction('add_payment', 'payment', data.id, { + reservation_id: reservationId, + amount, + type + }) + revalidatePath(`/dashboard/reservations/${reservationId}`) } @@ -34,6 +42,8 @@ export async function updateStatus(id: string, status: string) { if (error) throw new Error(error.message) + await logAction('update_reservation_status', 'reservation', id, { status }) + revalidatePath(`/dashboard/reservations/${id}`) revalidatePath('/dashboard/reservations') } diff --git a/src/app/dashboard/reservations/new/actions.ts b/src/app/dashboard/reservations/new/actions.ts index bf91d11..6750ef0 100644 --- a/src/app/dashboard/reservations/new/actions.ts +++ b/src/app/dashboard/reservations/new/actions.ts @@ -26,27 +26,62 @@ export async function createReservation(data: { if (conflictError) { throw new Error("Müsaitlik kontrolü yapılırken hata oluştu: " + conflictError.message) } + 'use server' - if (conflicts && conflicts.length > 0) { - return { error: "Seçilen tarih ve saatte bu salon zaten dolu." } + import { createClient } from "@/lib/supabase/server" + import { revalidatePath } from "next/cache" + import { redirect } from "next/navigation" + import { logAction } from "@/lib/logger" + + export async function createReservation(data: { + hall_id: string + customer_id: string + package_id?: string + start_time: string + end_time: string + notes?: string + }) { + const supabase = await createClient() + + // 1. Check for conflicts + // We look for any reservation in the same hall that overlaps with the requested time range + const { data: conflicts, error: conflictError } = await supabase + .from('reservations') + .select('id') + .eq('hall_id', data.hall_id) + .neq('status', 'cancelled') // Ignore cancelled bookings + .or(`and(start_time.lte.${data.end_time},end_time.gte.${data.start_time})`) + + if (conflictError) { + throw new Error("Müsaitlik kontrolü yapılırken hata oluştu: " + conflictError.message) + } + + if (conflicts && conflicts.length > 0) { + return { error: "Seçilen tarih ve saatte bu salon zaten dolu." } + } + + // 2. Create Reservation + const { data: newReservation, error } = await supabase.from('reservations').insert({ + hall_id: data.hall_id, + customer_id: data.customer_id, + package_id: data.package_id || null, + start_time: data.start_time, + end_time: data.end_time, + status: 'pending', // Default to pending, admin must confirm + notes: data.notes, + }).select().single() + + if (error) { + return { error: error.message } + } + + await logAction('create_reservation', 'reservation', newReservation.id, { + hall_id: data.hall_id, + customer_id: data.customer_id, + start_time: data.start_time + }) + + revalidatePath('/dashboard/reservations') + revalidatePath('/dashboard/calendar') + redirect('/dashboard/reservations') } - - // 2. Create Reservation - const { error } = await supabase.from('reservations').insert({ - hall_id: data.hall_id, - customer_id: data.customer_id, - package_id: data.package_id || null, - start_time: data.start_time, - end_time: data.end_time, - status: 'pending', // Default to pending, admin must confirm - notes: data.notes, - }) - - if (error) { - return { error: error.message } - } - - revalidatePath('/dashboard/reservations') - revalidatePath('/dashboard/calendar') - redirect('/dashboard/reservations') -} diff --git a/src/lib/logger.ts b/src/lib/logger.ts new file mode 100644 index 0000000..3ca7756 --- /dev/null +++ b/src/lib/logger.ts @@ -0,0 +1,21 @@ +import { createClient } from "@/lib/supabase/server" + +export async function logAction( + action: string, + entityType: string, + entityId: string, + details?: any +) { + const supabase = await createClient() + const { data: { user } } = await supabase.auth.getUser() + + if (!user) return + + await supabase.from('audit_logs').insert({ + user_id: user.id, + action, + entity_type: entityType, + entity_id: entityId, + details, + }) +} diff --git a/supabase_schema.sql b/supabase_schema.sql index f28e508..dd71b2c 100644 --- a/supabase_schema.sql +++ b/supabase_schema.sql @@ -95,3 +95,28 @@ $$ language plpgsql security definer; create trigger on_auth_user_created after insert on auth.users for each row execute procedure public.handle_new_user(); + +-- Create Audit Logs Table +create table audit_logs ( + id uuid default uuid_generate_v4() primary key, + user_id uuid references auth.users(id), + action text not null, + entity_type text not null, + entity_id uuid, + details jsonb, + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +-- RLS for Audit Logs +alter table audit_logs enable row level security; + +create policy "Admins can read all logs" on audit_logs + for select using ( + exists ( + select 1 from profiles + where profiles.id = auth.uid() and profiles.role = 'admin' + ) + ); + +create policy "Users can insert logs" on audit_logs + for insert with check (auth.uid() = user_id);