Feat: Implement Audit Logging and integrate into Reservation actions
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
import { createClient } from "@/lib/supabase/server"
|
import { createClient } from "@/lib/supabase/server"
|
||||||
import { revalidatePath } from "next/cache"
|
import { revalidatePath } from "next/cache"
|
||||||
|
|
||||||
|
import { logAction } from "@/lib/logger"
|
||||||
|
|
||||||
export async function addPayment(reservationId: string, formData: FormData) {
|
export async function addPayment(reservationId: string, formData: FormData) {
|
||||||
const supabase = await createClient()
|
const supabase = await createClient()
|
||||||
|
|
||||||
@@ -10,17 +12,23 @@ export async function addPayment(reservationId: string, formData: FormData) {
|
|||||||
const type = formData.get('type')
|
const type = formData.get('type')
|
||||||
const method = formData.get('method')
|
const method = formData.get('method')
|
||||||
|
|
||||||
const { error } = await supabase.from('payments').insert({
|
const { data, error } = await supabase.from('payments').insert({
|
||||||
reservation_id: reservationId,
|
reservation_id: reservationId,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
payment_type: type,
|
payment_type: type,
|
||||||
payment_method: method,
|
payment_method: method,
|
||||||
status: 'paid', // Assuming immediate payment for now
|
status: 'paid', // Assuming immediate payment for now
|
||||||
paid_at: new Date().toISOString(),
|
paid_at: new Date().toISOString(),
|
||||||
})
|
}).select().single()
|
||||||
|
|
||||||
if (error) throw new Error(error.message)
|
if (error) throw new Error(error.message)
|
||||||
|
|
||||||
|
await logAction('add_payment', 'payment', data.id, {
|
||||||
|
reservation_id: reservationId,
|
||||||
|
amount,
|
||||||
|
type
|
||||||
|
})
|
||||||
|
|
||||||
revalidatePath(`/dashboard/reservations/${reservationId}`)
|
revalidatePath(`/dashboard/reservations/${reservationId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +42,8 @@ export async function updateStatus(id: string, status: string) {
|
|||||||
|
|
||||||
if (error) throw new Error(error.message)
|
if (error) throw new Error(error.message)
|
||||||
|
|
||||||
|
await logAction('update_reservation_status', 'reservation', id, { status })
|
||||||
|
|
||||||
revalidatePath(`/dashboard/reservations/${id}`)
|
revalidatePath(`/dashboard/reservations/${id}`)
|
||||||
revalidatePath('/dashboard/reservations')
|
revalidatePath('/dashboard/reservations')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,35 @@ import { createClient } from "@/lib/supabase/server"
|
|||||||
import { revalidatePath } from "next/cache"
|
import { revalidatePath } from "next/cache"
|
||||||
import { redirect } from "next/navigation"
|
import { redirect } from "next/navigation"
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
'use server'
|
||||||
|
|
||||||
|
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: {
|
export async function createReservation(data: {
|
||||||
hall_id: string
|
hall_id: string
|
||||||
customer_id: string
|
customer_id: string
|
||||||
@@ -32,7 +61,7 @@ export async function createReservation(data: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Create Reservation
|
// 2. Create Reservation
|
||||||
const { error } = await supabase.from('reservations').insert({
|
const { data: newReservation, error } = await supabase.from('reservations').insert({
|
||||||
hall_id: data.hall_id,
|
hall_id: data.hall_id,
|
||||||
customer_id: data.customer_id,
|
customer_id: data.customer_id,
|
||||||
package_id: data.package_id || null,
|
package_id: data.package_id || null,
|
||||||
@@ -40,12 +69,18 @@ export async function createReservation(data: {
|
|||||||
end_time: data.end_time,
|
end_time: data.end_time,
|
||||||
status: 'pending', // Default to pending, admin must confirm
|
status: 'pending', // Default to pending, admin must confirm
|
||||||
notes: data.notes,
|
notes: data.notes,
|
||||||
})
|
}).select().single()
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return { error: error.message }
|
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/reservations')
|
||||||
revalidatePath('/dashboard/calendar')
|
revalidatePath('/dashboard/calendar')
|
||||||
redirect('/dashboard/reservations')
|
redirect('/dashboard/reservations')
|
||||||
|
|||||||
21
src/lib/logger.ts
Normal file
21
src/lib/logger.ts
Normal file
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -95,3 +95,28 @@ $$ language plpgsql security definer;
|
|||||||
create trigger on_auth_user_created
|
create trigger on_auth_user_created
|
||||||
after insert on auth.users
|
after insert on auth.users
|
||||||
for each row execute procedure public.handle_new_user();
|
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user