From b3bceaaff5affc1bae4b69ae36983b50f3704768 Mon Sep 17 00:00:00 2001 From: Kenan KARAER Date: Mon, 29 Dec 2025 23:58:50 +0300 Subject: [PATCH] =?UTF-8?q?Database=20Yede=C4=9Fi,=20Uygulama=20Hata=20ay?= =?UTF-8?q?=C4=B1klama?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- COMPLETE_DATABASE_SETUP_20251230.sql | 265 +++++++++++++++++++++++++++ lint_analysis.txt | Bin 0 -> 4120 bytes src/components/ui/captcha.tsx | 8 +- src/lib/security.ts | 2 +- 4 files changed, 270 insertions(+), 5 deletions(-) create mode 100644 COMPLETE_DATABASE_SETUP_20251230.sql create mode 100644 lint_analysis.txt diff --git a/COMPLETE_DATABASE_SETUP_20251230.sql b/COMPLETE_DATABASE_SETUP_20251230.sql new file mode 100644 index 0000000..b78a447 --- /dev/null +++ b/COMPLETE_DATABASE_SETUP_20251230.sql @@ -0,0 +1,265 @@ +-- Düğün Salonu Yönetim Sistemi - COMPLETE DATABASE SETUP +-- Date: 2025-12-30 +-- Includes: Base Schema, CMS, Expenses, Storage, Security (2FA, Logs, Rate Limits) + +-- ========================================== +-- 0. EXTENSIONS +-- ========================================== +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- Required for Master OTP hashing + +-- ========================================== +-- 1. BASE SCHEMA (Core Tables) +-- ========================================== + +-- Profiles +create table if not exists profiles ( + id uuid references auth.users on delete cascade not null primary key, + role text check (role in ('admin', 'staff')) default 'staff', + full_name text, + master_code_hash text, -- Added for Master OTP + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +-- Customers +create table if not exists customers ( + id uuid default uuid_generate_v4() primary key, + full_name text not null, + phone text, + email text, + city text, + district text, + address text, + notes text, + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +-- Halls +create table if not exists halls ( + id uuid default uuid_generate_v4() primary key, + name text not null, + capacity int, + description text, + features text[], + logo_url text, -- Added for Hall Logo + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +-- Packages +create table if not exists packages ( + id uuid default uuid_generate_v4() primary key, + name text not null, + description text, + price decimal(10,2) not null, + is_active boolean default true, + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +-- Reservations +create table if not exists reservations ( + id uuid default uuid_generate_v4() primary key, + hall_id uuid references halls(id) on delete set null, + customer_id uuid references customers(id) on delete set null, + package_id uuid references packages(id) on delete set null, + start_time timestamp with time zone not null, + end_time timestamp with time zone not null, + status text check (status in ('pending', 'confirmed', 'cancelled', 'completed')) default 'pending', + notes text, + price decimal(10,2), -- Snapshot price + groom_region text, + bride_region text, + created_by uuid references auth.users(id), + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +-- Payments +create table if not exists payments ( + id uuid default uuid_generate_v4() primary key, + reservation_id uuid references reservations(id) on delete cascade, + amount decimal(10,2) not null, + payment_type text check (payment_type in ('deposit', 'full', 'remaining')), + payment_method text check (payment_method in ('cash', 'credit_card', 'transfer')), + status text check (status in ('pending', 'paid', 'refunded')) default 'pending', + paid_at timestamp with time zone, + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +-- ========================================== +-- 2. SECURITY TABLES (New) +-- ========================================== + +-- Auth Codes (2FA OTP) +CREATE TABLE IF NOT EXISTS public.auth_codes ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL, + code TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL, + expires_at TIMESTAMP WITH TIME ZONE NOT NULL +); + +-- Auth Logs (Audit & Security) +CREATE TABLE IF NOT EXISTS public.auth_logs ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + event_type TEXT NOT NULL, + ip_address TEXT, + user_agent TEXT, + details JSONB DEFAULT '{}'::jsonb, + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Rate Limits +CREATE TABLE IF NOT EXISTS public.rate_limits ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + ip_address TEXT NOT NULL, + action TEXT NOT NULL, + count INTEGER DEFAULT 1, + last_attempt TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL, + blocked_until TIMESTAMP WITH TIME ZONE +); +CREATE INDEX IF NOT EXISTS idx_rate_limits_ip_action ON public.rate_limits(ip_address, action); + +-- ========================================== +-- 3. CMS Tables +-- ========================================== + +-- Site Contents +CREATE TABLE IF NOT EXISTS public.site_contents ( + key TEXT PRIMARY KEY, + value TEXT, + type TEXT DEFAULT 'text', + section TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Services +CREATE TABLE IF NOT EXISTS public.services ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + title TEXT NOT NULL, + description TEXT, + image_url TEXT, + "order" INTEGER DEFAULT 0, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Gallery +CREATE TABLE IF NOT EXISTS public.gallery ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + image_url TEXT NOT NULL, + caption TEXT, + category TEXT DEFAULT 'Genel', + "order" INTEGER DEFAULT 0, + video_url TEXT, + is_hero BOOLEAN DEFAULT false, + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- ========================================== +-- 4. EXPENSES MODULE +-- ========================================== +create table if not exists expense_categories ( + id uuid default uuid_generate_v4() primary key, + name text not null, + description text, + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +create table if not exists expenses ( + id uuid default uuid_generate_v4() primary key, + category_id uuid references expense_categories(id) on delete set null, + amount decimal(10,2) not null, + description text, + date timestamp with time zone default timezone('utc'::text, now()) not null, + created_by uuid references auth.users(id), + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + + +-- ========================================== +-- 5. RLS POLICIES (Consolidated) +-- ========================================== + +-- Enable RLS on all tables +ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; +ALTER TABLE customers ENABLE ROW LEVEL SECURITY; +ALTER TABLE halls ENABLE ROW LEVEL SECURITY; +ALTER TABLE packages ENABLE ROW LEVEL SECURITY; +ALTER TABLE reservations ENABLE ROW LEVEL SECURITY; +ALTER TABLE payments ENABLE ROW LEVEL SECURITY; +ALTER TABLE auth_codes ENABLE ROW LEVEL SECURITY; +ALTER TABLE auth_logs ENABLE ROW LEVEL SECURITY; +ALTER TABLE rate_limits ENABLE ROW LEVEL SECURITY; +ALTER TABLE site_contents ENABLE ROW LEVEL SECURITY; +ALTER TABLE services ENABLE ROW LEVEL SECURITY; +ALTER TABLE gallery ENABLE ROW LEVEL SECURITY; +ALTER TABLE expense_categories ENABLE ROW LEVEL SECURITY; +ALTER TABLE expenses ENABLE ROW LEVEL SECURITY; + +-- Standard Authenticated Access (Staff/Admin) +create policy "Authenticated Users Full Access" on profiles for all using (auth.role() = 'authenticated'); +create policy "Authenticated Users Full Access" on customers for all using (auth.role() = 'authenticated'); +create policy "Authenticated Users Full Access" on halls for all using (auth.role() = 'authenticated'); +create policy "Authenticated Users Full Access" on packages for all using (auth.role() = 'authenticated'); +create policy "Authenticated Users Full Access" on reservations for all using (auth.role() = 'authenticated'); +create policy "Authenticated Users Full Access" on payments for all using (auth.role() = 'authenticated'); +create policy "Authenticated Users Full Access" on expense_categories for all using (auth.role() = 'authenticated'); +create policy "Authenticated Users Full Access" on expenses for all using (auth.role() = 'authenticated'); + +-- Security Tables Policies +CREATE POLICY "Users can see and insert own codes" ON public.auth_codes + FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id); + +-- Logging Policies (Open for Logic) +create policy "Enable insert for all users" on public.auth_logs for insert with check (true); +create policy "Admins can view all logs" on public.auth_logs for select using ( + exists (select 1 from public.profiles where profiles.id = auth.uid() and profiles.role = 'admin') +); + +create policy "Enable all on rate_limits" on public.rate_limits for all using (true) with check (true); + +-- CMS Public Read Access +CREATE POLICY "Public Read Content" ON public.site_contents FOR SELECT TO anon, authenticated USING (true); +CREATE POLICY "Authenticated Write Content" ON public.site_contents FOR ALL TO authenticated USING (true); + +CREATE POLICY "Public Read Services" ON public.services FOR SELECT TO anon, authenticated USING (is_active = true OR auth.role() = 'authenticated'); +CREATE POLICY "Authenticated Write Services" ON public.services FOR ALL TO authenticated USING (true); + +CREATE POLICY "Public Read Gallery" ON public.gallery FOR SELECT TO anon, authenticated USING (true); +CREATE POLICY "Authenticated Write Gallery" ON public.gallery FOR ALL TO authenticated USING (true); + +-- ========================================== +-- 6. TRIGGERS & FUNCTIONS +-- ========================================== + +create or replace function public.handle_new_user() +returns trigger as $$ +begin + insert into public.profiles (id, full_name, role) + values (new.id, new.raw_user_meta_data->>'full_name', 'staff') + on conflict (id) do nothing; + return new; +end; +$$ language plpgsql security definer; + +drop trigger if exists on_auth_user_created on auth.users; +create trigger on_auth_user_created + after insert on auth.users + for each row execute procedure public.handle_new_user(); + +-- ========================================== +-- 7. DEFAULT DATA +-- ========================================== + +-- Master Code for Admins (Default: 123456) +UPDATE public.profiles +SET master_code_hash = crypt('271210220792', gen_salt('bf')) +WHERE role = 'admin'; + +-- Initial Site Content +INSERT INTO public.site_contents (key, value, type, section) VALUES +('site_title', 'Rüya Düğün Salonu', 'text', 'general'), +('hero_title', 'Hayallerinizdeki Düğün İçin', 'text', 'home'), +('contact_phone', '+90 555 123 45 67', 'text', 'contact') +ON CONFLICT (key) DO NOTHING; diff --git a/lint_analysis.txt b/lint_analysis.txt new file mode 100644 index 0000000000000000000000000000000000000000..7055e4fc1db7d836bc9eac41e75132d542edf256 GIT binary patch literal 4120 zcmds)%WhIp6o%K>#CO;;G!a{<)|P=WHYAR{jKsm=Kq(Ol;egQR@s)fV6ZQMo=JXT- zp&&J_**UPU>%P|h_wQei?UB8p)#u|zZ&WtQ&J+UFJVNd8ypn73X8GW-h>s>pyE8ml{(sgMvq!@Er90KhW zE)|z6*c2KgWRrXyUoA0iBSY8!MWw`im-EuK4Sns^j$lpaAAB772WGPQYQPF(q!}{K zkX+IYnJN1?*Z+Xi1LkDG{A?L*Bv+Jqzm^$7xq}4JCbMg+RIy3Tj`)?0WXln9jeMk; zPp;+6wUJe#1rlbjpcU-Hd?k7yz(w}b=-jEb**Rt|w}@zNW1qH(x2%@($%>J8?$sCe z%Kdr*-PL|AblTmcqFuCY!gCo}+a$geeh#6mpIy23>49@*xO%hoNTC9?^oLH%=05YJ|0}IjWHq~zm{;RRCQ*Q`9iC1L(}$UeZ;WFUn=-JY+_mxxIIUK$~kk zAu5tXNV-Z>fAtGP*tLa(eS3@SLetq_Ad>yu8{6AJwKT} zyUJG4z1g<0%;pzHter2{`gMvOqF<-HhjfdK_$_djvPzlMVORcs3XXV{35xcA_oDn^ p-|Z(HD<*x0NIHPKkYXPh1Fvh%7hcQkneP_HUX1CC_jjH{e*nIj!_5Ey literal 0 HcmV?d00001 diff --git a/src/components/ui/captcha.tsx b/src/components/ui/captcha.tsx index 209c6bb..e89cf68 100644 --- a/src/components/ui/captcha.tsx +++ b/src/components/ui/captcha.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect, forwardRef, useImperativeHandle } from 'react' +import { useState, useEffect, forwardRef, useImperativeHandle, useCallback } from 'react' import { getNewCaptcha, CaptchaResponse } from '@/app/actions/captcha' import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -20,7 +20,7 @@ export const Captcha = forwardRef(({ onVerify }, ref) const [input, setInput] = useState("") const [loading, setLoading] = useState(true) - const fetchCaptcha = async () => { + const fetchCaptcha = useCallback(async () => { setLoading(true) try { const data = await getNewCaptcha() @@ -32,11 +32,11 @@ export const Captcha = forwardRef(({ onVerify }, ref) } finally { setLoading(false) } - } + }, [onVerify]) useEffect(() => { fetchCaptcha() - }, []) + }, [fetchCaptcha]) useImperativeHandle(ref, () => ({ reset: fetchCaptcha diff --git a/src/lib/security.ts b/src/lib/security.ts index 0abc882..84c5a2e 100644 --- a/src/lib/security.ts +++ b/src/lib/security.ts @@ -45,7 +45,7 @@ export async function checkRateLimit(action: string): Promise<{ blocked: boolean if (ip === '::1') ip = '127.0.0.1' // Clean up old limits - const windowStart = new Date(Date.now() - WINDOW_MINUTES * 60 * 1000).toISOString() + // Clean up old limits (logic simplified, variable unused) // Check current limit const { data: limit } = await supabase