feat: Foundation — auth system, 9 migrations, React frontend

Backend:
- Express server with JWT httpOnly cookie auth
- POST /api/auth/register, /api/auth/login, /api/auth/logout, GET /api/auth/me
- bcrypt 12 rounds, generic 401 errors (no email/password field disclosure)
- Auth middleware protects all /api/* routes except register/login
- pg Pool database connection

Frontend (React + Vite + TailwindCSS + shadcn/ui):
- AuthContext with session restore on page load via /api/auth/me
- ProtectedRoute redirects unauthenticated users to /login
- LoginPage, RegisterPage — Hebrew RTL layout (dir=rtl), inline validation
- DashboardPage placeholder
- shadcn/ui components: Button, Input, Label, Card

Database:
- 9 migrations (001-009): extensions, users, events, vendors, guests,
  bookings, invitations, vendor_ratings, organizer_preferences
- pg_trgm for fuzzy Hebrew search, GIN indexes on style_tags
- Phase 2+3 fields included: source, payment_status, contract_value,
  vendor ratings 6-dimension, organizer preferences
- Idempotent migration runner with schema_migrations tracking table

Infrastructure:
- Dockerfile (multi-stage: build React → production node:20-alpine)
- docker-compose.yml with PostgreSQL healthcheck, expose not ports
- Migrations run automatically on container start

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 18:22:42 +00:00
parent 0f1882e9ae
commit c8909befb1
45 changed files with 5669 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
-- Seed 001: Test data for development
-- Passwords are bcrypt hashes of 'Password123!' — replace at runtime with actual hash
BEGIN;
-- ─── Users ───────────────────────────────────────────────────────────────────
-- Test organizer
INSERT INTO users (id, email, password_hash, display_name, role) VALUES
('11111111-1111-1111-1111-111111111111',
'organizer@test.com',
'$2b$10$REPLACE_WITH_REAL_HASH',
'מארגן בדיקה',
'organizer');
-- Vendor users (one per vendor profile below)
INSERT INTO users (id, email, password_hash, display_name, role) VALUES
('22222222-2222-2222-2222-222222222222', 'catering@test.com', '$2b$10$REPLACE_WITH_REAL_HASH', 'קייטרינג לדוגמה', 'vendor'),
('22222222-2222-2222-2222-222222222223', 'photography@test.com', '$2b$10$REPLACE_WITH_REAL_HASH', 'צלם לדוגמה', 'vendor'),
('22222222-2222-2222-2222-222222222224', 'music@test.com', '$2b$10$REPLACE_WITH_REAL_HASH', 'מוזיקה לדוגמה', 'vendor'),
('22222222-2222-2222-2222-222222222225', 'decoration@test.com', '$2b$10$REPLACE_WITH_REAL_HASH', 'עיצוב לדוגמה', 'vendor'),
('22222222-2222-2222-2222-222222222226', 'venue@test.com', '$2b$10$REPLACE_WITH_REAL_HASH', 'אולם לדוגמה', 'vendor');
-- ─── Vendors (5 profiles across different categories) ────────────────────────
INSERT INTO vendors (id, user_id, business_name, category, city, geographic_area,
base_price, price_range_min, price_range_max,
capacity_min, capacity_max,
style_tags, is_verified) VALUES
('33333333-3333-3333-3333-333333333331',
'22222222-2222-2222-2222-222222222222',
'קייטרינג שף אורי', 'catering', 'תל אביב', 'מרכז',
5000.00, 4000.00, 12000.00, 50, 500,
ARRAY['kosher_mehadrin', 'modern'], TRUE),
('33333333-3333-3333-3333-333333333332',
'22222222-2222-2222-2222-222222222223',
'סטודיו לכידת רגעים', 'photography', 'ירושלים', 'ירושלים וסביבותיה',
3000.00, 2500.00, 8000.00, 30, 600,
ARRAY['traditional', 'religious'], TRUE),
('33333333-3333-3333-3333-333333333333',
'22222222-2222-2222-2222-222222222224',
'להקת הכוכבים', 'music', 'חיפה', 'צפון',
4000.00, 3000.00, 10000.00, 100, 800,
ARRAY['modern', 'mizrahi'], FALSE),
('33333333-3333-3333-3333-333333333334',
'22222222-2222-2222-2222-222222222225',
'עיצוב ואווירה', 'decoration', 'ראשון לציון', 'מרכז',
2000.00, 1500.00, 6000.00, 20, 400,
ARRAY['rustic', 'romantic', 'modern'], TRUE),
('33333333-3333-3333-3333-333333333335',
'22222222-2222-2222-2222-222222222226',
'אולם הנשיאים', 'venue', 'נתניה', 'שרון',
15000.00, 10000.00, 35000.00, 80, 700,
ARRAY['elegant', 'modern'], TRUE);
-- ─── Sample Event ─────────────────────────────────────────────────────────────
INSERT INTO events (id, organizer_id, title, description, event_date,
venue_name, max_guests, venue_capacity,
status, kashrut_level, noise_curfew_time,
max_plus_ones_buffer, retention_policy_days, language_pref) VALUES
('44444444-4444-4444-4444-444444444444',
'11111111-1111-1111-1111-111111111111',
'חתונת בדיקה',
'אירוע לדוגמה לפיתוח',
NOW() + INTERVAL '30 days',
'אולם הנשיאים', 150, 200,
'draft', 'mehadrin', '23:00',
30, 365, 'hebrew');
-- ─── Sample Guests ────────────────────────────────────────────────────────────
INSERT INTO guests (event_id, name_hebrew, name_transliteration, email, phone,
rsvp_status, relationship_group, dietary_preference,
plus_one_allowance, source, privacy_accepted_at) VALUES
('44444444-4444-4444-4444-444444444444',
'יוסי כהן', 'Yossi Cohen', 'yossi@test.com', '+972501234567',
'pending', 'family_groom', 'kosher_mehadrin', 1, 'registered', NOW()),
('44444444-4444-4444-4444-444444444444',
'מיכל לוי', 'Michal Levi', 'michal@test.com', '+972521234567',
'confirmed', 'friends', 'vegetarian', 0, 'registered', NOW()),
('44444444-4444-4444-4444-444444444444',
'דוד ישראלי', 'David Israeli', 'david@test.com', '+972541234567',
'declined', 'work', 'none', 0, 'registered', NOW());
COMMIT;