diff --git a/full_database.sql b/full_database.sql new file mode 100644 index 0000000..8f8a862 --- /dev/null +++ b/full_database.sql @@ -0,0 +1,387 @@ +-- Düğün Salonu Yönetim Sistemi - Full Database Schema & Data +-- Generated by Antigravity + +-- ========================================== +-- 1. BASE SCHEMA (Core Tables) +-- ========================================== + +-- Enable UUID extension +create extension if not exists "uuid-ossp"; + +-- Create Profiles Table (Extends Auth) +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, + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +-- Create Customers Table (CRM) +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 +); + +-- Create Halls Table (Salons) +create table if not exists halls ( + id uuid default uuid_generate_v4() primary key, + name text not null, + capacity int, + description text, + features text[], + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +-- Create Packages Table (Pricing) +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 +); + +-- Create Reservations Table +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, + created_by uuid references auth.users(id), + created_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +-- Create Payments Table +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 +); + +-- RLS Policies for Base 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; + +-- Drop existing policies to avoid conflicts if re-running +drop policy if exists "Enable all access for authenticated users" on profiles; +drop policy if exists "Enable all access for authenticated users" on customers; +drop policy if exists "Enable all access for authenticated users" on halls; +drop policy if exists "Enable all access for authenticated users" on packages; +drop policy if exists "Enable all access for authenticated users" on reservations; +drop policy if exists "Enable all access for authenticated users" on payments; + +create policy "Enable all access for authenticated users" on profiles for all using (auth.role() = 'authenticated'); +create policy "Enable all access for authenticated users" on customers for all using (auth.role() = 'authenticated'); +create policy "Enable all access for authenticated users" on halls for all using (auth.role() = 'authenticated'); +create policy "Enable all access for authenticated users" on packages for all using (auth.role() = 'authenticated'); +create policy "Enable all access for authenticated users" on reservations for all using (auth.role() = 'authenticated'); +create policy "Enable all access for authenticated users" on payments for all using (auth.role() = 'authenticated'); + +-- Auth Trigger +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 +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(); + +-- Create Audit Logs Table +create table if not exists 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; + +drop policy if exists "Admins can read all logs" on audit_logs; +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' + ) + ); + +drop policy if exists "Users can insert logs" on audit_logs; +create policy "Users can insert logs" on audit_logs + for insert with check (auth.uid() = user_id); + + +-- ========================================== +-- 2. CMS Tables & Seeds +-- ========================================== + +-- 1. Site Contents Table (Genel İçerikler) +CREATE TABLE IF NOT EXISTS public.site_contents ( + key TEXT PRIMARY KEY, + value TEXT, + type TEXT DEFAULT 'text', -- 'text', 'image_url', 'html', 'json' + section TEXT, -- 'home', 'about', 'contact', 'footer' + 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 +); + +-- RLS: Public read, Admin write +ALTER TABLE public.site_contents ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Allow public read access on site_contents" ON public.site_contents; +CREATE POLICY "Allow public read access on site_contents" +ON public.site_contents FOR SELECT TO anon, authenticated +USING (true); + +DROP POLICY IF EXISTS "Allow authenticated update on site_contents" ON public.site_contents; +CREATE POLICY "Allow authenticated update on site_contents" +ON public.site_contents FOR UPDATE TO authenticated +USING (true) +WITH CHECK (true); + +DROP POLICY IF EXISTS "Allow authenticated insert on site_contents" ON public.site_contents; +CREATE POLICY "Allow authenticated insert on site_contents" +ON public.site_contents FOR INSERT TO authenticated +WITH CHECK (true); + +-- 2. Services Table (Hizmetler) +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 +); + +ALTER TABLE public.services ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Allow public read on services" ON public.services; +CREATE POLICY "Allow public read on services" +ON public.services FOR SELECT TO anon, authenticated +USING (is_active = true OR auth.role() = 'authenticated'); + +DROP POLICY IF EXISTS "Allow admin all on services" ON public.services; +CREATE POLICY "Allow admin all on services" +ON public.services FOR ALL TO authenticated +USING (true) +WITH CHECK (true); + +-- 3. Gallery Table (Galeri) +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, + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +ALTER TABLE public.gallery ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS "Allow public read on gallery" ON public.gallery; +CREATE POLICY "Allow public read on gallery" +ON public.gallery FOR SELECT TO anon, authenticated +USING (true); + +DROP POLICY IF EXISTS "Allow admin all on gallery" ON public.gallery; +CREATE POLICY "Allow admin all on gallery" +ON public.gallery FOR ALL TO authenticated +USING (true) +WITH CHECK (true); + +-- Seed Data (Initial 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'), +('hero_subtitle', 'Unutulmaz anlar, profesyonel hizmet ve şık atmosfer.', 'text', 'home'), +('contact_phone', '+90 555 123 45 67', 'text', 'contact'), +('contact_address', 'Atatürk Mah. Karanfil Sok. No:5, İstanbul', 'text', 'contact'), +('site_logo', '', 'image_url', 'general') +ON CONFLICT (key) DO NOTHING; + +-- ========================================== +-- 3. Expenses Module +-- ========================================== + +-- Create Expense Categories Table +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 Expenses Table +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 +); + +-- RLS +alter table expense_categories enable row level security; +alter table expenses enable row level security; + +drop policy if exists "Enable all access for authenticated users" on expense_categories; +drop policy if exists "Enable all access for authenticated users" on expenses; + +create policy "Enable all access for authenticated users" on expense_categories for all using (auth.role() = 'authenticated'); +create policy "Enable all access for authenticated users" on expenses for all using (auth.role() = 'authenticated'); + + +-- ========================================== +-- 4. Storage Setup +-- ========================================== + +-- 1. Create the storage bucket +insert into storage.buckets (id, name, public) +values ('hall-logos', 'hall-logos', true) +on conflict (id) do nothing; + +insert into storage.buckets (id, name, public) +values ('public-site', 'public-site', true) +on conflict (id) do nothing; + +-- 2. Enable RLS +-- Hall Logos Policies +drop policy if exists "Public Access" on storage.objects; +create policy "Public Access" + on storage.objects for select + using ( bucket_id = 'hall-logos' ); + +drop policy if exists "Authenticated Uploads" on storage.objects; +create policy "Authenticated Uploads" + on storage.objects for insert + with check ( bucket_id = 'hall-logos' and auth.role() = 'authenticated' ); + +drop policy if exists "Authenticated Updates" on storage.objects; +create policy "Authenticated Updates" + on storage.objects for update + with check ( bucket_id = 'hall-logos' and auth.role() = 'authenticated' ); + +drop policy if exists "Authenticated Deletes" on storage.objects; +create policy "Authenticated Deletes" + on storage.objects for delete + using ( bucket_id = 'hall-logos' and auth.role() = 'authenticated' ); + + +-- Public Site Bucket Policies +-- Allow public read SPECIFIC to this bucket +DROP POLICY IF EXISTS "Public Access public-site" ON storage.objects; +CREATE POLICY "Public Access public-site" +ON storage.objects FOR SELECT +TO public +USING ( bucket_id = 'public-site' ); + +-- Allow authenticated upload/delete SPECIFIC to this bucket +DROP POLICY IF EXISTS "Authenticated Insert public-site" ON storage.objects; +CREATE POLICY "Authenticated Insert public-site" +ON storage.objects FOR INSERT +TO authenticated +WITH CHECK ( bucket_id = 'public-site' ); + +DROP POLICY IF EXISTS "Authenticated Update public-site" ON storage.objects; +CREATE POLICY "Authenticated Update public-site" +ON storage.objects FOR UPDATE +TO authenticated +USING ( bucket_id = 'public-site' ); + +DROP POLICY IF EXISTS "Authenticated Delete public-site" ON storage.objects; +CREATE POLICY "Authenticated Delete public-site" +ON storage.objects FOR DELETE +TO authenticated +USING ( bucket_id = 'public-site' ); + + +-- ========================================== +-- 5. Schema Updates (Halls) +-- ========================================== + +-- Add logo_url column to halls table +alter table halls +add column if not exists logo_url text; + + +-- ========================================== +-- 6. Schema Updates (Reservations) +-- ========================================== + +-- Add price column to reservations table +alter table reservations add column if not exists price decimal(10,2); + +-- Update existing reservations to set price from their package (snapshotting the price) +update reservations +set price = packages.price +from packages +where reservations.package_id = packages.id + and reservations.price is null; + +-- Add groom_region and bride_region to reservations table +ALTER TABLE reservations +ADD COLUMN IF NOT EXISTS groom_region text, +ADD COLUMN IF NOT EXISTS bride_region text; + + +-- ========================================== +-- 7. Schema Updates (Gallery) +-- ========================================== + +ALTER TABLE public.gallery ADD COLUMN IF NOT EXISTS video_url TEXT; +ALTER TABLE public.gallery ADD COLUMN IF NOT EXISTS is_hero BOOLEAN DEFAULT false; + + +-- ========================================== +-- 8. Additional Data (Site Contents) +-- ========================================== + +INSERT INTO public.site_contents (key, value, type, section) +VALUES ('contact_email', 'info@ruyadugun.com', 'text', 'contact') +ON CONFLICT (key) DO NOTHING; + +INSERT INTO public.site_contents (key, value, type, section) VALUES +('social_instagram', 'https://instagram.com/', 'text', 'contact'), +('social_facebook', 'https://facebook.com/', 'text', 'contact'), +('social_twitter', 'https://twitter.com/', 'text', 'contact') +ON CONFLICT (key) DO NOTHING; + +INSERT INTO public.site_contents (key, value, type, section) +VALUES ('contact_map_embed', '', 'html', 'contact') +ON CONFLICT (key) DO NOTHING; diff --git a/package-lock.json b/package-lock.json index 46d5311..8430209 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "lucide-react": "^0.555.0", "next": "15.0.3", "next-themes": "^0.4.6", + "pg": "^8.16.3", "react": "18.3.1", "react-big-calendar": "^1.19.4", "react-day-picker": "^9.11.3", @@ -7363,6 +7364,95 @@ "dev": true, "license": "MIT" }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7421,6 +7511,45 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8107,6 +8236,15 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -8918,6 +9056,15 @@ } } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index 3adbd17..d798de6 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "lucide-react": "^0.555.0", "next": "15.0.3", "next-themes": "^0.4.6", + "pg": "^8.16.3", "react": "18.3.1", "react-big-calendar": "^1.19.4", "react-day-picker": "^9.11.3",