Index: nv
===================================================================
--- .env	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,14 +1,0 @@
-# Database Connection (via SSH tunnel on localhost:9999)
-DB_USER=db_202526z_va_prj_wedding_planner2025_owner
-DB_HOST=localhost
-DB_PORT=9999
-DB_NAME=db_202526z_va_prj_wedding_planner2025
-DB_PASSWORD=63fc1f01aa0b
-
-# Email Configuration
-EMAIL_USER=marijataseska800@gmail.com
-EMAIL_PASS=mtwk ykzx ymoq wvup
-
-# Server Configuration
-PORT=3000
-NODE_ENV=development
Index: itignore
===================================================================
--- .gitignore	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,18 +1,0 @@
-# Environment variables
-.env
-.env.local
-.env.*.local
-
-# Dependencies
-node_modules/
-package-lock.json
-
-# IDE
-.vscode/
-.vs/
-*.swp
-*.swo
-
-# OS
-.DS_Store
-desktop.ini
Index: README.md
===================================================================
--- README.md	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ README.md	(revision ff03305a6dbed8553581b6e2f5ab39423a67eaa6)
@@ -1,65 +1,2 @@
-\# Wedding Planner Management System
-
-
-
-\*\*Course:\*\* Database Systems
-
-\*\*Academic Year:\*\* 2025/2026
-
-
-
-\## Project Overview
-
-
-
-Wedding Planner Management System is a database-driven project developed to support the planning, organization, and management of wedding events. The system centralizes wedding-related information, including venues, ceremonies, bands, photographers, guests, RSVPs, attendance records, and reservations.
-
-
-
-The project focuses on efficient data management, scheduling, conflict prevention, analytical reporting, performance optimization, and database security. By maintaining structured relationships between entities, the system helps prevent issues such as double-booking, overlapping reservations, and inconsistent event data.
-
-
-
-The implementation was developed using PostgreSQL and includes database design, business logic, analytical reports, indexing and optimization strategies, transaction management, and security mechanisms.
-
-
-
-\## Team Members
-
-
-
-\* Marija Taseska (213087)
-
-\* Jana Trpkovska (193284)
-
-\* Slobodanka Pishtolova (211171)
-
-\* Ivan Ivanovski (212093)
-
-
-
-\## Project Documentation
-
-
-
-\* ER Model: https://develop.finki.ukim.mk/projects/wedding\_planner2025/wiki/ERModel
-
-\* Phase 2: https://develop.finki.ukim.mk/projects/wedding_planner2025/wiki/RelationalDesign
-
-\* Phase 3: https://develop.finki.ukim.mk/projects/wedding\_planner2025/wiki/UseCaseModel
-
-\* Phase 4: https://develop.finki.ukim.mk/projects/wedding\_planner2025/wiki/BuildInstructions 
-            https://develop.finki.ukim.mk/projects/wedding\_planner2025/wiki/PrototypeImplementation
-
-\* Phase 5: https://develop.finki.ukim.mk/projects/wedding\_planner2025/wiki/Normalization
-
-\* Phase 6: https://develop.finki.ukim.mk/projects/wedding\_planner2025/wiki/AdvancedReports
-
-\* Phase 7: https://develop.finki.ukim.mk/projects/wedding\_planner2025/wiki/AdvancedDatabaseDevelopment
-
-\* Phase 8: https://develop.finki.ukim.mk/projects/wedding\_planner2025/wiki/AdvancedApplicationDevelopment
-
-\* Phase 9: https://develop.finki.ukim.mk/projects/wedding\_planner2025/wiki/OtherTopics
-
-
-
+# Wedding-Planner-
+Wedding Planner System
Index: dding_Planner.html
===================================================================
--- Wedding_Planner.html	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,1894 +1,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <title>Wedding Planner</title>
-    <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet"/>
-    <style>
-        :root {
-            --cream:#fdf8f3; --cream-2:#f9f1e8; --cream-3:#f2e8db;
-            --blush-light:#fce8e8; --blush:#f4b8b8; --blush-mid:#e8949c;
-            --rose:#d4606a; --rose-deep:#b84550;
-            --gold-light:#f7e6b0; --gold:#c9a84c; --gold-dark:#9e7a28;
-            --text-dark:#2c1f1f; --text-mid:#6b4f4f; --text-light:#a08080;
-            --border:#ecddd0; --border-light:#f4ebe2; --white:#ffffff;
-            --green-light:#e4f5ec; --green:#3a9e6b;
-            --amber-light:#fff4e0; --amber:#c87f2a;
-            --red-light:#fdeaea; --red:#c94545;
-            --purple-light:#f0eaff; --purple:#7c55c8;
-            --sidebar-w:248px; --header-h:64px;
-            --radius-sm:6px; --radius-md:10px; --radius-lg:16px; --radius-xl:22px;
-            --shadow-xs:0 1px 3px rgba(44,31,31,.06),0 1px 2px rgba(44,31,31,.04);
-            --shadow-sm:0 2px 8px rgba(44,31,31,.08),0 1px 3px rgba(44,31,31,.05);
-            --shadow-md:0 6px 20px rgba(44,31,31,.10),0 2px 6px rgba(44,31,31,.06);
-            --shadow-lg:0 16px 40px rgba(44,31,31,.14),0 4px 12px rgba(44,31,31,.08);
-            --transition:200ms cubic-bezier(.4,0,.2,1);
-        }
-        *,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
-        html,body{height:100%;}
-        body{font-family:'DM Sans',sans-serif;background:var(--cream);color:var(--text-dark);display:flex;overflow:hidden;-webkit-font-smoothing:antialiased;}
-        ::-webkit-scrollbar{width:6px;height:6px;}
-        ::-webkit-scrollbar-track{background:transparent;}
-        ::-webkit-scrollbar-thumb{background:var(--blush);border-radius:99px;}
-
-        /* ── Sidebar ── */
-        .sidebar{width:var(--sidebar-w);min-height:100vh;background:#fff;border-right:1px solid var(--border);display:flex;flex-direction:column;position:fixed;left:0;top:0;bottom:0;z-index:100;overflow-y:auto;overflow-x:hidden;box-shadow:var(--shadow-sm);}
-        .sidebar-header{padding:22px 18px 18px;border-bottom:1px solid var(--border-light);display:flex;align-items:center;gap:10px;flex-shrink:0;}
-        .logo-mark{width:36px;height:36px;flex-shrink:0;background:linear-gradient(135deg,var(--rose),var(--blush-mid));border-radius:9px;display:flex;align-items:center;justify-content:center;box-shadow:0 3px 12px rgba(212,96,106,.35);}
-        .logo-mark svg{width:18px;height:18px;}
-        .logo-text{display:flex;flex-direction:column;gap:1px;}
-        .logo-text .brand{font-family:'Cormorant Garamond',serif;font-size:17px;font-weight:700;color:var(--text-dark);line-height:1.1;}
-        .logo-text .sub{font-size:9px;font-weight:600;color:var(--text-light);text-transform:uppercase;letter-spacing:.14em;}
-        .sidebar-section-label{font-size:9.5px;font-weight:700;color:var(--text-light);text-transform:uppercase;letter-spacing:.1em;padding:20px 20px 6px;}
-        .nav-item{display:flex;align-items:center;gap:9px;padding:9px 14px;margin:1px 8px;border-radius:var(--radius-md);cursor:pointer;font-size:13.5px;font-weight:450;color:var(--text-mid);transition:background var(--transition),color var(--transition);text-decoration:none;white-space:nowrap;}
-        .nav-item svg{width:16px;height:16px;flex-shrink:0;opacity:.65;transition:opacity var(--transition);}
-        .nav-item:hover{background:var(--cream-2);color:var(--text-dark);}
-        .nav-item:hover svg{opacity:1;}
-        .nav-item.active{background:var(--blush-light);color:var(--rose);font-weight:600;}
-        .nav-item.active svg{opacity:1;}
-        .nav-badge{margin-left:auto;background:var(--blush-light);color:var(--rose);font-size:10px;font-weight:700;padding:2px 8px;border-radius:20px;flex-shrink:0;}
-        .sidebar-footer{margin-top:auto;padding:14px 12px;border-top:1px solid var(--border-light);flex-shrink:0;}
-        .user-card{display:flex;align-items:center;gap:10px;padding:10px;border-radius:var(--radius-md);cursor:pointer;transition:background var(--transition);}
-        .user-card:hover{background:var(--cream-2);}
-        .user-avatar{width:34px;height:34px;border-radius:50%;flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--blush-mid),var(--rose));color:white;font-weight:700;font-size:12px;}
-        .user-info{display:flex;flex-direction:column;gap:1px;overflow:hidden;}
-        .user-name{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
-        .user-role{font-size:11px;color:var(--text-light);}
-
-        /* ── Main ── */
-        .main{margin-left:var(--sidebar-w);flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden;}
-        .topbar{height:var(--header-h);background:#fff;border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 28px;gap:12px;position:sticky;top:0;z-index:50;flex-shrink:0;}
-        .breadcrumb{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text-light);}
-        .breadcrumb .bc-root{font-family:'Cormorant Garamond',serif;font-size:15px;font-weight:600;color:var(--text-mid);}
-        .breadcrumb .bc-active{color:var(--text-dark);font-weight:600;}
-        .breadcrumb-sep{width:4px;height:4px;background:var(--border);border-radius:50%;flex-shrink:0;}
-        .topbar-actions{margin-left:auto;display:flex;align-items:center;gap:8px;}
-        .icon-btn{position:relative;width:36px;height:36px;border-radius:var(--radius-md);border:1px solid var(--border);background:#fff;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background var(--transition),border-color var(--transition);}
-        .icon-btn:hover{background:var(--cream-2);border-color:var(--blush);}
-        .icon-btn svg{width:15px;height:15px;stroke:var(--text-mid);}
-        .page-content{padding:28px;overflow-y:auto;height:calc(100vh - var(--header-h));}
-
-        /* ── Buttons ── */
-        .btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 16px;border-radius:var(--radius-md);font-family:'DM Sans',sans-serif;font-size:13px;font-weight:500;border:1px solid var(--border);background:#fff;color:var(--text-dark);cursor:pointer;white-space:nowrap;transition:background var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition);text-decoration:none;line-height:1.4;}
-        .btn:hover{background:var(--cream-2);border-color:var(--blush);}
-        .btn:active{transform:translateY(1px);}
-        .btn-primary{background:var(--rose);color:#fff;border-color:var(--rose);box-shadow:0 2px 8px rgba(212,96,106,.30);}
-        .btn-primary:hover{background:var(--rose-deep);border-color:var(--rose-deep);}
-        .btn-gold{background:var(--gold);color:#fff;border-color:var(--gold);}
-        .btn-gold:hover{background:var(--gold-dark);border-color:var(--gold-dark);}
-        .btn-ghost{background:transparent;color:var(--text-mid);border-color:transparent;}
-        .btn-ghost:hover{background:var(--cream-2);border-color:var(--border);}
-        .btn-danger{background:var(--red-light);color:var(--red);border-color:#f5c2c2;}
-        .btn-danger:hover{background:#fbd5d5;border-color:var(--red);}
-        .btn-sm{padding:5px 10px;font-size:12px;border-radius:var(--radius-sm);}
-        .btn-icon{padding:6px;border-radius:var(--radius-sm);}
-
-        /* ── Cards ── */
-        .card{background:#fff;border-radius:var(--radius-xl);box-shadow:var(--shadow-md);border:1px solid var(--border-light);padding:22px;}
-        .card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px;}
-        .card-header h3{font-family:'Cormorant Garamond',serif;font-size:20px;font-weight:700;color:var(--text-dark);}
-        .card-header .card-actions{display:flex;gap:8px;align-items:center;}
-        h1,h2,h3,h4{margin:0;}
-        .muted{color:var(--text-light);font-size:13px;}
-        .serif{font-family:'Cormorant Garamond',serif;}
-
-        /* ── Dashboard grid ── */
-        .dash-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;}
-        .dash-full{grid-column:1/-1;}
-
-        /* ── Stats ── */
-        .stats-row{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:20px;}
-        .stat-card{background:#fff;border:1px solid var(--border-light);border-radius:var(--radius-lg);padding:16px 14px;display:flex;flex-direction:column;gap:5px;box-shadow:var(--shadow-xs);}
-        .stat-icon{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;margin-bottom:4px;}
-        .stat-icon svg{width:15px;height:15px;}
-        .stat-label{font-size:10.5px;font-weight:700;color:var(--text-light);text-transform:uppercase;letter-spacing:.07em;}
-        .stat-value{font-family:'Cormorant Garamond',serif;font-size:28px;font-weight:700;line-height:1;}
-        .stat-sub{font-size:11px;color:var(--text-light);}
-        .stat-rose .stat-icon{background:var(--blush-light);}.stat-rose .stat-icon svg{stroke:var(--rose);}.stat-rose .stat-value{color:var(--rose);}
-        .stat-gold .stat-icon{background:var(--gold-light);}.stat-gold .stat-icon svg{stroke:var(--gold-dark);}.stat-gold .stat-value{color:var(--gold-dark);}
-        .stat-green .stat-icon{background:var(--green-light);}.stat-green .stat-icon svg{stroke:var(--green);}.stat-green .stat-value{color:var(--green);}
-        .stat-amber .stat-icon{background:var(--amber-light);}.stat-amber .stat-icon svg{stroke:var(--amber);}.stat-amber .stat-value{color:var(--amber);}
-        .stat-purple .stat-icon{background:var(--purple-light);}.stat-purple .stat-icon svg{stroke:var(--purple);}.stat-purple .stat-value{color:var(--purple);}
-
-        /* ── Badges ── */
-        .badge{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:999px;font-weight:600;font-size:11px;line-height:1.6;}
-        .badge-dot{width:6px;height:6px;border-radius:50%;}
-        .badge-rose{background:var(--blush-light);color:var(--rose-deep);}
-        .badge-gold{background:var(--gold-light);color:var(--gold-dark);}
-        .badge-success{background:var(--green-light);color:#1e7a4f;}
-        .badge-warn{background:var(--amber-light);color:#7a4e10;}
-        .badge-danger{background:var(--red-light);color:#8b1f1f;}
-        .badge-info{background:#e6f0ff;color:#1a4fa0;}
-        .badge-purple{background:var(--purple-light);color:#4a2d9e;}
-
-        /* ── Gold rule ── */
-        .gold-rule{width:36px;height:2px;background:linear-gradient(90deg,var(--gold),transparent);border-radius:2px;margin:10px 0 14px;}
-
-        /* ── Table ── */
-        .table-card{background:#fff;border:1px solid var(--border-light);border-radius:var(--radius-xl);box-shadow:var(--shadow-xs);overflow:hidden;}
-        .table-toolbar{padding:14px 18px;display:flex;align-items:center;gap:10px;border-bottom:1px solid var(--border-light);flex-wrap:wrap;}
-        .search-wrap{position:relative;flex:1;min-width:200px;}
-        .search-wrap input{padding-left:32px;background:#fff;}
-        .search-wrap svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);width:14px;height:14px;stroke:var(--text-light);}
-        .table-wrap{overflow-x:auto;}
-        table{width:100%;border-collapse:collapse;font-size:13.5px;}
-        thead{background:var(--cream);border-bottom:1px solid var(--border-light);}
-        th{padding:11px 14px;text-align:left;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:var(--text-light);white-space:nowrap;cursor:default;}
-        th.sortable{cursor:pointer;user-select:none;}
-        th.sortable:hover{color:var(--text-dark);}
-        .sort-arrow{margin-left:4px;opacity:.5;font-size:10px;}
-        .sort-arrow.asc,.sort-arrow.desc{opacity:1;color:var(--rose);}
-        td{padding:11px 14px;border-bottom:1px solid var(--border-light);vertical-align:middle;}
-        tr:last-child td{border-bottom:none;}
-        tr.row-even td{background:var(--cream);}
-        tr:hover td{background:var(--blush-light);}
-        .td-actions{display:flex;gap:6px;align-items:center;}
-        .pagination-bar{display:flex;justify-content:space-between;align-items:center;padding:10px 18px;border-top:1px solid var(--border-light);font-size:12px;color:var(--text-light);}
-        .pag-controls{display:flex;gap:4px;}
-        .pag-btn{padding:4px 9px;border-radius:var(--radius-sm);border:1px solid var(--border);background:#fff;font-size:12px;cursor:pointer;}
-        .pag-btn:hover:not(:disabled){background:var(--cream-2);}
-        .pag-btn.active{background:var(--rose);color:#fff;border-color:var(--rose);}
-        .pag-btn:disabled{opacity:.4;cursor:default;}
-
-        /* ── Forms ── */
-        .form-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
-        .form-group{display:flex;flex-direction:column;gap:5px;}
-        .form-label{font-size:11.5px;font-weight:600;color:var(--text-mid);text-transform:uppercase;letter-spacing:.06em;}
-        input,select,textarea{width:100%;padding:9px 12px;border-radius:var(--radius-md);border:1px solid var(--border);background:var(--cream);font-family:'DM Sans',sans-serif;font-size:13.5px;color:var(--text-dark);outline:none;transition:border-color var(--transition),background var(--transition);}
-        input:focus,select:focus,textarea:focus{border-color:var(--blush-mid);background:#fff;}
-
-        /* ── Info list ── */
-        .info-list{display:flex;flex-direction:column;gap:10px;}
-        .info-row{display:flex;justify-content:space-between;align-items:center;}
-        .info-row .info-label{font-size:12px;color:var(--text-light);font-weight:500;}
-        .info-row .info-val{font-size:13.5px;font-weight:600;color:var(--text-dark);}
-
-        /* ── Toast ── */
-        .toast{position:fixed;bottom:24px;right:24px;z-index:9999;background:var(--text-dark);color:#fff;padding:12px 20px;border-radius:var(--radius-lg);font-size:13.5px;font-weight:500;box-shadow:var(--shadow-lg);display:flex;align-items:center;gap:10px;transform:translateY(10px);opacity:0;transition:transform .25s ease,opacity .25s ease;pointer-events:none;}
-        .toast.show{transform:translateY(0);opacity:1;}
-        .toast.toast-success{background:var(--green);}
-        .toast.toast-danger{background:var(--red);}
-
-        /* ── No data ── */
-        .no-data-row td{text-align:center;padding:40px 20px;color:var(--text-light);font-style:italic;}
-
-        /* ── Modal ── */
-        .modal-backdrop{display:none;position:fixed;inset:0;background:rgba(44,31,31,.45);z-index:1000;align-items:center;justify-content:center;padding:20px;}
-        .modal-backdrop.open{display:flex;}
-        .modal{background:#fff;border-radius:var(--radius-xl);box-shadow:var(--shadow-lg);width:100%;max-width:520px;max-height:90vh;overflow-y:auto;}
-        .modal-header{display:flex;justify-content:space-between;align-items:center;padding:20px 24px 0;}
-        .modal-header strong{font-family:'Cormorant Garamond',serif;font-size:20px;font-weight:700;}
-        .modal-body{padding:20px 24px;}
-        .modal-actions{display:flex;justify-content:flex-end;gap:8px;padding:0 24px 20px;}
-
-        /* ── Seating board ── */
-        .seating-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin-top:16px;}
-        .table-card-seat{background:var(--cream);border:1px solid var(--border);border-radius:var(--radius-lg);padding:16px;transition:box-shadow var(--transition);}
-        .table-card-seat:hover{box-shadow:var(--shadow-sm);}
-        .table-card-seat .table-title{font-family:'Cormorant Garamond',serif;font-size:16px;font-weight:700;color:var(--rose);margin-bottom:10px;display:flex;align-items:center;gap:6px;}
-        .table-card-seat .seat-guest{display:flex;align-items:center;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--border-light);font-size:13px;}
-        .table-card-seat .seat-guest:last-child{border-bottom:none;}
-        .table-card-seat .seat-role{font-size:11px;color:var(--text-light);}
-        .unassigned-list{margin-top:12px;display:flex;flex-direction:column;gap:6px;}
-        .unassigned-item{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#fff;border:1px solid var(--border-light);border-radius:var(--radius-md);font-size:13px;}
-
-        /* ── Responsive ── */
-        @media(max-width:1100px){.stats-row{grid-template-columns:repeat(3,1fr);}}
-        @media(max-width:900px){.dash-grid{grid-template-columns:1fr;}.dash-full{grid-column:1;}}
-        @media(max-width:768px){.sidebar{width:60px;}.sidebar .logo-text,.sidebar .nav-item span:not(.nav-badge),.sidebar .sidebar-section-label,.sidebar .user-info{display:none;}.main{margin-left:60px;}.stats-row{grid-template-columns:1fr 1fr;}}
-        @media(max-width:540px){.page-content{padding:14px;}.modal-body .form-grid{grid-template-columns:1fr;}}
-    </style>
-</head>
-<body>
-
-<!-- SIDEBAR -->
-<aside class="sidebar" id="sidebar">
-    <div class="sidebar-header">
-        <div class="logo-mark">
-            <svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
-                <path d="M12 21.593c-5.63-5.539-11-10.297-11-14.402C1 3.402 4.248 1 7.5 1 9.849 1 11.271 2.007 12 3.011 12.729 2.007 14.151 1 16.5 1 19.752 1 23 3.402 23 7.191c0 4.105-5.37 8.863-11 14.402z"/>
-            </svg>
-        </div>
-        <div class="logo-text">
-            <span class="brand">Wedding Planner</span>
-            <span class="sub">Plan Your Perfect Day</span>
-        </div>
-    </div>
-
-    <div class="sidebar-section-label">Main</div>
-    <a class="nav-item active" href="#/dashboard" data-page="dashboard">
-        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
-        <span>Dashboard</span>
-    </a>
-    <a class="nav-item" href="#/event" data-page="event">
-        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
-        <span>Events</span>
-    </a>
-    <a class="nav-item" href="#/guests" data-page="guests">
-        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
-        <span>Guests</span>
-        <span class="nav-badge" id="badge-guests">0</span>
-    </a>
-    <a class="nav-item" href="#/seating" data-page="seating">
-        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/><line x1="12" y1="12" x2="12" y2="16"/><line x1="10" y1="14" x2="14" y2="14"/></svg>
-        <span>Seating</span>
-    </a>
-    <a class="nav-item" href="#/venue" data-page="venue">
-        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
-        <span>Venue</span>
-    </a>
-    <a class="nav-item" href="#/band" data-page="band">
-        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
-        <span>Band</span>
-    </a>
-    <a class="nav-item" href="#/photographer" data-page="photographer">
-        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
-        <span>Photographer</span>
-    </a>
-    <a class="nav-item" href="#/church" data-page="church">
-        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="2" x2="12" y2="8"/><line x1="9" y1="5" x2="15" y2="5"/><path d="M4 10l8 4 8-4"/><path d="M4 10v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/></svg>
-        <span>Church</span>
-    </a>
-    <a class="nav-item" href="#/registrar" data-page="registrar">
-        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
-        <span>Registrar</span>
-    </a>
-
-    <div class="sidebar-section-label">Account</div>
-    <a class="nav-item" href="#/settings" data-page="settings">
-        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
-        <span>Settings</span>
-    </a>
-    <a class="nav-item" href="#" id="btnLogout" style="color:var(--red);">
-        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
-        <span>Logout</span>
-    </a>
-
-    <div class="sidebar-footer">
-        <div class="user-card">
-            <div class="user-avatar" id="userAvatar">??</div>
-            <div class="user-info">
-                <div class="user-name" id="userName">Loading...</div>
-                <div class="user-role">Wedding Owner</div>
-            </div>
-        </div>
-    </div>
-</aside>
-
-<!-- MAIN -->
-<div class="main" id="app">
-    <header class="topbar">
-        <div class="breadcrumb" id="breadcrumb">
-            <span class="bc-root">Wedding Planner</span>
-            <div class="breadcrumb-sep"></div>
-            <span id="breadcrumb-page" class="bc-active">Dashboard</span>
-        </div>
-        <div class="topbar-actions">
-            <button class="icon-btn" title="Search" onclick="focusSearch()">
-                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
-            </button>
-        </div>
-    </header>
-    <section class="page-content" id="page-content"></section>
-</div>
-
-<!-- MODAL -->
-<div class="modal-backdrop" id="modalBackdrop">
-    <div class="modal" id="modal">
-        <div class="modal-header">
-            <strong id="modalTitle">Form</strong>
-            <button class="btn btn-ghost btn-icon" onclick="closeModal()">
-                <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
-            </button>
-        </div>
-        <div class="modal-body" id="modalBody"></div>
-        <div class="modal-actions">
-            <button class="btn btn-ghost" onclick="closeModal()">Cancel</button>
-            <button class="btn btn-primary" id="modalSubmit">Save</button>
-        </div>
-    </div>
-</div>
-
-<div class="toast" id="toast"></div>
-
-<script>
-// =============================================================
-//  CONFIG
-// =============================================================
-const API = 'http://localhost:3000';
-
-// =============================================================
-//  APP STATE  — loaded from DB, not hardcoded
-// =============================================================
-let CURRENT_USER   = null;
-let CURRENT_WEDDING = null;
-let WEDDINGS = [];
-let EVENTS         = [];
-let GUESTS         = [];
-let VENUES         = [];
-let VENUE_BOOKINGS = [];
-let PHOTOGRAPHERS  = [];
-let PHOTOGRAPHER_BOOKINGS = [];
-let BANDS          = [];
-let BAND_BOOKINGS  = [];
-let CHURCHES       = [];
-let CHURCH_BOOKINGS = [];
-let REGISTRARS     = [];
-let REGISTRAR_BOOKINGS = [];
-let ATTENDANCE     = [];
-let RSVPS = [];
-let BUDGET_STATUS  = null;  // { predefined_budget, total_cost, exceeded, over_by, ... }
-
-// =============================================================
-//  COUPLE NAMES (local-only, used for invitation emails)
-//  Stored in localStorage — never sent to/stored in the database.
-// =============================================================
-const COUPLE_NAMES_KEY = 'wp_couple_names';
-function getCoupleNames(){
-    try {
-        const raw = localStorage.getItem(COUPLE_NAMES_KEY);
-        if(!raw) return { bride_name:'', groom_name:'' };
-        const parsed = JSON.parse(raw);
-        return { bride_name: parsed.bride_name || '', groom_name: parsed.groom_name || '' };
-    } catch(e){ return { bride_name:'', groom_name:'' }; }
-}
-function saveCoupleNames(bride_name, groom_name){
-    localStorage.setItem(COUPLE_NAMES_KEY, JSON.stringify({ bride_name, groom_name }));
-}
-
-// Show alert + toast when a booking pushes total spending over the predefined budget
-function showBudgetAlert(budget) {
-    if (!budget || !budget.exceeded) return;
-    const over = Number(budget.over_by).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
-    const spent = Number(budget.total_cost).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
-    const predefined = Number(budget.predefined_budget).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
-    const msg = `⚠️ Budget Exceeded!\n\nPredefined budget: $${predefined}\nSpent so far: $${spent}\nOver by: $${over}`;
-    showToast(`Budget exceeded by $${over}`, 'danger');
-    alert(msg);
-}
-
-// =============================================================
-//  BUDGET CALCULATION (runs entirely from already-loaded data)
-//  Venue   : guests × price_per_guest  (per confirmed booking)
-//  Band    : hours × price_per_hour    (booking_cost from server)
-//  Photo   : hours × price_per_hour    (booking_cost from server)
-// =============================================================
-function calcBudgetSpent() {
-    const guestCount = GUESTS.length;
-
-    const venueCost = VENUE_BOOKINGS.reduce((sum, b) => {
-        const venue = VENUES.find(v => String(v.venue_id) === String(b.venue_id));
-        const ppg   = venue ? Number(venue.price_per_guest || 0) : 0;
-        return sum + ppg * guestCount;
-    }, 0);
-
-    // Calculate hours from time strings "HH:MM" or "HH:MM:SS"
-    const toHours = (start, end) => {
-        const toMin = t => { const p = String(t).split(':').map(Number); return p[0]*60+(p[1]||0); };
-        return Math.max(0, (toMin(end) - toMin(start)) / 60);
-    };
-
-    const bandCost = BAND_BOOKINGS.reduce((sum, b) => {
-        const hours = b.hours ? Number(b.hours) : toHours(b.start_time, b.end_time);
-        return sum + hours * Number(b.price_per_hour || 0);
-    }, 0);
-
-    const photoCost = PHOTOGRAPHER_BOOKINGS.reduce((sum, b) => {
-        const hours = b.hours ? Number(b.hours) : toHours(b.start_time, b.end_time);
-        return sum + hours * Number(b.price_per_hour || 0);
-    }, 0);
-
-    const total      = venueCost + bandCost + photoCost;
-    const predefined = CURRENT_WEDDING ? Number(CURRENT_WEDDING.budget || 0) : 0;
-    const exceeded   = predefined > 0 && total > predefined;
-
-    return { venueCost, bandCost, photoCost, total, predefined, exceeded, over_by: exceeded ? +(total - predefined).toFixed(2) : 0 };
-}
-
-// Check and show alert if budget is exceeded — call after any operation that changes cost/guests
-function checkBudgetAndAlert() {
-    if (!CURRENT_WEDDING || !CURRENT_WEDDING.budget) return;
-    const s = calcBudgetSpent();
-    if (s.exceeded) {
-        const over = Number(s.over_by).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
-        const spent = Number(s.total).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
-        const predefined = Number(s.predefined).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
-        showToast(`⚠️ Budget exceeded by $${over}`, 'danger');
-        alert(`⚠️ Budget Exceeded!\n\nPredefined budget:  $${predefined}\nSpent so far:       $${spent}\nOver by:            $${over}\n\nBreakdown:\n  Venue:          $${Number(s.venueCost).toLocaleString()}\n  Band:           $${Number(s.bandCost).toLocaleString()}\n  Photographer:   $${Number(s.photoCost).toLocaleString()}`);
-    }
-}
-
-// =============================================================
-//  API HELPER
-// =============================================================
-async function api(method, path, body) {
-    const opts = {
-        method,
-        credentials: 'include',
-        headers: { 'Content-Type': 'application/json' }
-    };
-    if (body) opts.body = JSON.stringify(body);
-    const res = await fetch(`${API}${path}`, opts);
-    if (res.status === 401) { window.location.href = 'login.html'; return null; }
-    return res;
-}
-
-// =============================================================
-//  BOOTSTRAP — check auth + load all data
-// =============================================================
-async function bootstrap() {
-    // 1. Check login
-    const meRes = await fetch(`${API}/api/auth/me`, { credentials: 'include' });
-    if (!meRes.ok) { window.location.href = 'login.html'; return; }
-    CURRENT_USER = (await meRes.json()).user;
-
-    // Update sidebar user info
-    document.getElementById('userName').textContent = `${CURRENT_USER.first_name} ${CURRENT_USER.last_name}`;
-    document.getElementById('userAvatar').textContent = `${CURRENT_USER.first_name[0]}${CURRENT_USER.last_name[0]}`;
-
-    // 2. Load wedding
-    const wRes = await api('GET', '/api/weddings');
-    if (wRes && wRes.ok) {
-        const weddings = await wRes.json();
-        WEDDINGS = weddings;
-        CURRENT_WEDDING = weddings[0] || null;
-    }
-
-    if (!CURRENT_WEDDING) {
-        // No wedding yet — show create wedding prompt
-        if (!location.hash || location.hash === '#/dashboard') {
-            renderApp();
-            return;
-        }
-    }
-
-    const wid = CURRENT_WEDDING ? CURRENT_WEDDING.wedding_id : null;
-
-    // 3. Load all data in parallel
-    if (wid) {
-        const [evR, guR, vbR, pbR, bbR, rbR, cbR, attR] = await Promise.all([
-            api('GET', `/api/weddings/${wid}/events`),
-            api('GET', `/api/weddings/${wid}/guests`),
-            api('GET', `/api/weddings/${wid}/venue-bookings`),
-            api('GET', `/api/weddings/${wid}/photographer-bookings`),
-            api('GET', `/api/weddings/${wid}/band-bookings`),
-            api('GET', `/api/weddings/${wid}/registrar-bookings`),
-            api('GET', `/api/weddings/${wid}/church-bookings`),
-            api('GET', `/api/weddings/${wid}/attendance`)
-        ]);
-        if (evR && evR.ok) EVENTS = await evR.json();
-        if (guR && guR.ok) GUESTS = await guR.json();
-        if (vbR && vbR.ok) VENUE_BOOKINGS = await vbR.json();
-        if (pbR && pbR.ok) PHOTOGRAPHER_BOOKINGS = await pbR.json();
-        if (bbR && bbR.ok) BAND_BOOKINGS = await bbR.json();
-        if (rbR && rbR.ok) REGISTRAR_BOOKINGS = await rbR.json();
-        if (cbR && cbR.ok) CHURCH_BOOKINGS = await cbR.json();
-        if (attR && attR.ok) ATTENDANCE = await attR.json();
-        // Load budget status (predefined vs spent)
-        const budR = await api('GET', `/api/weddings/${wid}/budget`);
-        if (budR && budR.ok) BUDGET_STATUS = await budR.json();
-    }
-
-    // Load RSVPs for this wedding (admin view)
-    if (wid) {
-        const rsvpR = await api('GET', `/api/weddings/${wid}/rsvp`);
-        if (rsvpR && rsvpR.ok) RSVPS = await rsvpR.json();
-    }
-
-    const [venR, banR, phR, chR, regR] = await Promise.all([
-        api('GET', '/api/venues'),
-        api('GET', '/api/bands'),
-        api('GET', '/api/photographers'),
-        api('GET', '/api/churches'),
-        api('GET', '/api/registrars')
-    ]);
-    if (venR && venR.ok) VENUES = await venR.json();
-    if (banR && banR.ok) BANDS = await banR.json();
-    if (phR && phR.ok) PHOTOGRAPHERS = await phR.json();
-    if (chR && chR.ok) CHURCHES = await chR.json();
-    if (regR && regR.ok) REGISTRARS = await regR.json();
-
-    renderApp();
-}
-
-// Reload all wedding-specific data then re-render
-async function reload() {
-    if (!CURRENT_WEDDING) return;
-    // Refresh list of weddings first (in case user created/removed/switches)
-    try {
-        const wListR = await api('GET', '/api/weddings');
-        if (wListR && wListR.ok) {
-            WEDDINGS = await wListR.json();
-            // keep CURRENT_WEDDING reference in sync
-            if (CURRENT_WEDDING) {
-                const found = WEDDINGS.find(w => String(w.wedding_id) === String(CURRENT_WEDDING.wedding_id));
-                if (found) CURRENT_WEDDING = found;
-            }
-        }
-    } catch (e) { /* ignore */ }
-
-    const wid = CURRENT_WEDDING.wedding_id;
-    const [evR, guR, vbR, pbR, bbR, rbR, cbR, attR] = await Promise.all([
-        api('GET', `/api/weddings/${wid}/events`),
-        api('GET', `/api/weddings/${wid}/guests`),
-        api('GET', `/api/weddings/${wid}/venue-bookings`),
-        api('GET', `/api/weddings/${wid}/photographer-bookings`),
-        api('GET', `/api/weddings/${wid}/band-bookings`),
-        api('GET', `/api/weddings/${wid}/registrar-bookings`),
-        api('GET', `/api/weddings/${wid}/church-bookings`),
-        api('GET', `/api/weddings/${wid}/attendance`)
-    ]);
-    if (evR && evR.ok) EVENTS = await evR.json();
-    if (guR && guR.ok) GUESTS = await guR.json();
-    if (vbR && vbR.ok) VENUE_BOOKINGS = await vbR.json();
-    if (pbR && pbR.ok) PHOTOGRAPHER_BOOKINGS = await pbR.json();
-    if (bbR && bbR.ok) BAND_BOOKINGS = await bbR.json();
-    if (rbR && rbR.ok) REGISTRAR_BOOKINGS = await rbR.json();
-    if (cbR && cbR.ok) CHURCH_BOOKINGS = await cbR.json();
-    if (attR && attR.ok) ATTENDANCE = await attR.json();
-    // Refresh RSVP list as well
-    const rsvpR = await api('GET', `/api/weddings/${wid}/rsvp`);
-    if (rsvpR && rsvpR.ok) RSVPS = await rsvpR.json();
-    // Refresh budget status
-    const budR = await api('GET', `/api/weddings/${wid}/budget`);
-    if (budR && budR.ok) BUDGET_STATUS = await budR.json();
-    renderApp();
-}
-
-// =============================================================
-//  ROUTER
-// =============================================================
-const ROUTE_LABELS = {
-    '/dashboard':'Dashboard','/event':'Events','/guests':'Guests',
-    '/seating':'Table Seating','/venue':'Venue','/band':'Band',
-    '/photographer':'Photographer','/church':'Church',
-    '/registrar':'Registrar','/settings':'Settings'
-};
-const ROUTE_RENDERERS = {
-    '/dashboard':renderDashboard,'/event':renderEventPage,
-    '/guests':renderGuestsPage,'/seating':renderSeatingPage,
-    '/venue':renderVenuePage,'/band':renderBandPage,
-    '/photographer':renderPhotographerPage,'/church':renderChurchPage,
-    '/registrar':renderRegistrarPage,'/settings':renderSettingsPage
-};
-
-function navigate(hash){ window.location.hash = hash; }
-
-function renderApp(){
-    const rawPath = window.location.hash.replace('#','') || '/dashboard';
-    const ctx = rawPath.split('?')[0];
-    document.getElementById('breadcrumb-page').textContent = ROUTE_LABELS[ctx] || 'Dashboard';
-    document.querySelectorAll('.nav-item').forEach(a => {
-        const page = a.getAttribute('data-page');
-        a.classList.toggle('active', page ? `/${page}` === ctx : false);
-    });
-    // Render wedding selector in the topbar actions so user can switch between weddings
-    try {
-        const topbarActions = document.querySelector('.topbar .topbar-actions');
-        // remove old selector if present
-        const oldSel = document.getElementById('weddingSelect');
-        if (oldSel) oldSel.remove();
-        const oldBtn = document.getElementById('btnNewWedding');
-        if (oldBtn) oldBtn.remove();
-
-        // Create selector
-        const sel = document.createElement('select');
-        sel.id = 'weddingSelect';
-        sel.style.minWidth = '200px';
-        sel.style.padding = '6px 8px';
-        sel.style.borderRadius = '8px';
-        sel.style.border = '1px solid var(--border)';
-        sel.style.background = '#fff';
-        sel.style.marginRight = '8px';
-        const noneOpt = document.createElement('option');
-        noneOpt.value = '';
-        noneOpt.textContent = '— Select Wedding —';
-        sel.appendChild(noneOpt);
-        WEDDINGS.forEach(w => {
-            const o = document.createElement('option');
-            o.value = w.wedding_id;
-            o.textContent = `${w.date} ${w.notes?'- '+(w.notes||'') : ''}`;
-            if (CURRENT_WEDDING && String(CURRENT_WEDDING.wedding_id) === String(w.wedding_id)) o.selected = true;
-            sel.appendChild(o);
-        });
-        sel.addEventListener('change', async (e) => {
-            const val = e.target.value;
-            if (!val) return;
-            const found = WEDDINGS.find(x => String(x.wedding_id) === String(val));
-            if (found) {
-                CURRENT_WEDDING = found;
-                await reload();
-                renderApp();
-            }
-        });
-        topbarActions.insertBefore(sel, topbarActions.firstChild);
-
-        // New wedding button
-        const newBtn = document.createElement('button');
-        newBtn.id = 'btnNewWedding';
-        newBtn.className = 'btn btn-sm';
-        newBtn.style.marginRight = '8px';
-        newBtn.textContent = '+ Wedding';
-        newBtn.addEventListener('click', openCreateWeddingModal);
-        topbarActions.insertBefore(newBtn, topbarActions.firstChild);
-    } catch (e) { /* ignore UI insertion errors */ }
-    const content = document.getElementById('page-content');
-    content.innerHTML = '';
-    (ROUTE_RENDERERS[ctx] || renderDashboard)(content);
-    syncBadges();
-}
-
-window.addEventListener('hashchange', renderApp);
-document.addEventListener('DOMContentLoaded', () => {
-    if (!location.hash) location.hash = '#/dashboard';
-    bootstrap();
-});
-
-function syncBadges(){
-    const g = document.getElementById('badge-guests');
-    if(g) g.textContent = String(GUESTS.length);
-}
-
-// Logout
-document.getElementById('btnLogout').addEventListener('click', async (e) => {
-    e.preventDefault();
-    await api('POST','/api/auth/logout');
-    window.location.href = 'login.html';
-});
-
-// =============================================================
-//  TOAST
-// =============================================================
-let toastTimer = null;
-function showToast(msg, type=''){
-    const t = document.getElementById('toast');
-    t.textContent = msg;
-    t.className = 'toast show' + (type ? ` toast-${type}` : '');
-    clearTimeout(toastTimer);
-    toastTimer = setTimeout(() => t.classList.remove('show'), 3200);
-}
-
-// =============================================================
-//  MODAL
-// =============================================================
-function showModal(title, bodyHtml, onSubmit){
-    document.getElementById('modalTitle').textContent = title;
-    document.getElementById('modalBody').innerHTML = bodyHtml;
-    document.getElementById('modalBackdrop').classList.add('open');
-    const btn = document.getElementById('modalSubmit');
-    btn.onclick = onSubmit;
-}
-function closeModal(){
-    document.getElementById('modalBackdrop').classList.remove('open');
-}
-document.getElementById('modalBackdrop').addEventListener('click', e => {
-    if(e.target === document.getElementById('modalBackdrop')) closeModal();
-});
-
-// =============================================================
-//  DATA TABLE BUILDER
-// =============================================================
-function buildDataTable({ container, columns, rows, onEdit, onDelete }){
-    container.innerHTML = '';
-    const tableCard = document.createElement('div');
-    tableCard.className = 'table-card';
-
-    // toolbar / search
-    const toolbar = document.createElement('div');
-    toolbar.className = 'table-toolbar';
-    const searchWrap = document.createElement('div');
-    searchWrap.className = 'search-wrap';
-    searchWrap.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg><input class="search-input" placeholder="Search..."/>`;
-    toolbar.appendChild(searchWrap);
-    tableCard.appendChild(toolbar);
-
-    const tableWrap = document.createElement('div');
-    tableWrap.className = 'table-wrap';
-    const table = document.createElement('table');
-    const thead = document.createElement('thead');
-    const headRow = document.createElement('tr');
-
-    let sortKey = null, sortDir = 'asc';
-    let currentData = [...rows];
-    let currentPage = 1;
-    const pageSize = 8;
-
-    columns.forEach(col => {
-        const th = document.createElement('th');
-        if(col.sortable !== false) th.classList.add('sortable');
-        th.innerHTML = col.header + (col.sortable !== false ? '<span class="sort-arrow">&#8597;</span>' : '');
-        if(col.sortable !== false){
-            th.addEventListener('click', () => {
-                sortKey = col.accessor;
-                sortDir = sortKey === col.accessor && sortDir === 'asc' ? 'desc' : 'asc';
-                sortAndRender();
-            });
-        }
-        headRow.appendChild(th);
-    });
-    const thAct = document.createElement('th');
-    thAct.textContent = 'Actions'; thAct.style.textAlign = 'right';
-    headRow.appendChild(thAct);
-    thead.appendChild(headRow);
-    table.appendChild(thead);
-
-    const tbody = document.createElement('tbody');
-    table.appendChild(tbody);
-    tableWrap.appendChild(table);
-    tableCard.appendChild(tableWrap);
-
-    const pagBar = document.createElement('div');
-    pagBar.className = 'pagination-bar';
-    const pagInfo = document.createElement('span');
-    const pagCtrls = document.createElement('div');
-    pagCtrls.className = 'pag-controls';
-    pagBar.appendChild(pagInfo);
-    pagBar.appendChild(pagCtrls);
-    tableCard.appendChild(pagBar);
-    container.appendChild(tableCard);
-
-    function renderRows(){
-        tbody.innerHTML = '';
-        const total = currentData.length;
-        const totalPages = Math.max(1, Math.ceil(total / pageSize));
-        if(currentPage > totalPages) currentPage = totalPages;
-        const start = (currentPage-1)*pageSize;
-        const slice = currentData.slice(start, start+pageSize);
-
-        pagInfo.textContent = total === 0 ? 'No results' : `${start+1}–${Math.min(start+pageSize,total)} of ${total}`;
-
-        pagCtrls.innerHTML = '';
-        const mkBtn = (label, val, disabled, isActive) => {
-            const b = document.createElement('button');
-            b.className = 'pag-btn' + (isActive ? ' active' : '');
-            b.innerHTML = label; b.disabled = disabled;
-            if(!disabled) b.addEventListener('click', () => { currentPage = val; renderRows(); });
-            return b;
-        };
-        pagCtrls.appendChild(mkBtn('&laquo;',1,currentPage===1,false));
-        pagCtrls.appendChild(mkBtn('&lsaquo;',currentPage-1,currentPage===1,false));
-        for(let p=Math.max(1,currentPage-1);p<=Math.min(totalPages,currentPage+1);p++)
-            pagCtrls.appendChild(mkBtn(p,p,false,p===currentPage));
-        pagCtrls.appendChild(mkBtn('&rsaquo;',currentPage+1,currentPage===totalPages,false));
-        pagCtrls.appendChild(mkBtn('&raquo;',totalPages,currentPage===totalPages,false));
-
-        if(slice.length === 0){
-            const tr = document.createElement('tr'); tr.className = 'no-data-row';
-            const td = document.createElement('td'); td.colSpan = columns.length+1; td.textContent = 'No records found.';
-            tr.appendChild(td); tbody.appendChild(tr); return;
-        }
-
-        slice.forEach((row, idx) => {
-            const tr = document.createElement('tr');
-            tr.className = (start+idx)%2===1 ? 'row-even' : '';
-            columns.forEach(col => {
-                const td = document.createElement('td');
-                td.innerHTML = col.render ? col.render(row[col.accessor], row) : (row[col.accessor] ?? '');
-                tr.appendChild(td);
-            });
-            const tdAct = document.createElement('td');
-            tdAct.style.textAlign = 'right';
-            const actWrap = document.createElement('div');
-            actWrap.className = 'td-actions'; actWrap.style.justifyContent = 'flex-end';
-            if(onEdit){ const b = document.createElement('button'); b.className='btn btn-sm'; b.textContent='Edit'; b.addEventListener('click',()=>onEdit(row)); actWrap.appendChild(b); }
-            if(onDelete){ const b = document.createElement('button'); b.className='btn btn-sm btn-danger'; b.textContent='Delete'; b.addEventListener('click',()=>onDelete(row)); actWrap.appendChild(b); }
-            tdAct.appendChild(actWrap); tr.appendChild(tdAct); tbody.appendChild(tr);
-        });
-    }
-
-    function sortAndRender(){
-        if(sortKey){
-            currentData.sort((a,b)=>{
-                const va=(a[sortKey]??'').toString(), vb=(b[sortKey]??'').toString();
-                if(!isNaN(va)&&!isNaN(vb)) return sortDir==='asc'?Number(va)-Number(vb):Number(vb)-Number(va);
-                return sortDir==='asc'?va.localeCompare(vb):vb.localeCompare(va);
-            });
-        }
-        renderRows();
-    }
-
-    searchWrap.querySelector('input').addEventListener('input', e => {
-        const q = e.target.value.trim().toLowerCase();
-        currentData = q ? rows.filter(r=>Object.values(r).join(' ').toLowerCase().includes(q)) : [...rows];
-        currentPage = 1; sortAndRender();
-    });
-
-    sortAndRender();
-}
-
-// =============================================================
-//  NO WEDDING STATE
-// =============================================================
-function renderNoWedding(container){
-    container.innerHTML = `
-    <div class="card" style="text-align:center;padding:48px 32px;">
-        <div style="font-size:48px;margin-bottom:16px;">💍</div>
-        <h2 style="font-family:'Cormorant Garamond',serif;font-size:26px;margin-bottom:8px;">No Wedding Created Yet</h2>
-        <p class="muted" style="margin-bottom:24px;">Create your wedding to start planning your perfect day.</p>
-        <button class="btn btn-primary" onclick="openCreateWeddingModal()">+ Create Wedding</button>
-    </div>`;
-}
-
-function openCreateWeddingModal(){
-    showModal('Create Your Wedding',
-        `<div class="form-grid">
-            <div class="form-group" style="grid-column:1/-1;">
-                <label class="form-label">Wedding Date</label>
-                <input id="wDate" type="date"/>
-            </div>
-            <div class="form-group">
-                <label class="form-label">Budget ($)</label>
-                <input id="wBudget" type="number" placeholder="15000"/>
-            </div>
-            <div class="form-group" style="grid-column:1/-1;">
-                <label class="form-label">Notes / Theme</label>
-                <textarea id="wNotes" rows="3" placeholder="Summer garden theme..."></textarea>
-            </div>
-        </div>`,
-        async () => {
-            const date = document.getElementById('wDate').value;
-            if(!date){ showToast('Please select a wedding date.','danger'); return; }
-            const res = await api('POST','/api/weddings',{
-                date,
-                budget: document.getElementById('wBudget').value || null,
-                notes:  document.getElementById('wNotes').value || null
-            });
-            if(res && res.ok){
-                CURRENT_WEDDING = await res.json();
-                closeModal();
-                showToast('Wedding created! 🎉','success');
-                await reload();
-            } else {
-                showToast('Could not create wedding.','danger');
-            }
-        }
-    );
-}
-
-// =============================================================
-//  DASHBOARD
-// =============================================================
-function renderDashboard(container){
-    if(!CURRENT_WEDDING){ renderNoWedding(container); return; }
-    const w = CURRENT_WEDDING;
-    const daysLeft = Math.max(0, Math.ceil((new Date(w.date)-new Date())/86400000));
-
-    container.innerHTML = `
-    <div style="margin-bottom:20px;">
-        <h2 style="font-family:'Cormorant Garamond',serif;font-size:26px;font-weight:700;">
-            Welcome back, <em style="color:var(--rose);">${CURRENT_USER.first_name}</em> 👋
-        </h2>
-        <p class="muted" style="margin-top:4px;">Your wedding overview for <strong>${w.date}</strong></p>
-    </div>
-    <div class="stats-row">
-        <div class="stat-card stat-rose">
-            <div class="stat-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="3" y1="10" x2="21" y2="10"/></svg></div>
-            <div class="stat-label">Days Left</div>
-            <div class="stat-value">${daysLeft}</div>
-            <div class="stat-sub">Until the big day</div>
-        </div>
-        <div class="stat-card stat-gold">
-            <div class="stat-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 2l3 7H21l-5.5 4 2 7L12 16l-5.5 4 2-7L3 9h6z"/></svg></div>
-            <div class="stat-label">Budget</div>
-            <div class="stat-value">${w.budget ? '$'+(w.budget/1000).toFixed(0)+'k' : '—'}</div>
-            <div class="stat-sub">Predefined budget</div>
-        </div>
-        <div class="stat-card stat-green">
-            <div class="stat-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></div>
-            <div class="stat-label">Guests</div>
-            <div class="stat-value">${GUESTS.length}</div>
-            <div class="stat-sub">Invited guests</div>
-        </div>
-        <div class="stat-card stat-amber">
-            <div class="stat-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></div>
-            <div class="stat-label">Venues</div>
-            <div class="stat-value">${VENUE_BOOKINGS.length}</div>
-            <div class="stat-sub">Booked</div>
-        </div>
-        <div class="stat-card stat-purple">
-            <div class="stat-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/></svg></div>
-            <div class="stat-label">Events</div>
-            <div class="stat-value">${EVENTS.length}</div>
-            <div class="stat-sub">Scheduled</div>
-        </div>
-    </div>
-    <div class="dash-grid">
-        <div class="card">
-            <div class="card-header"><h3>Wedding Overview</h3><span class="badge badge-rose"><span class="badge-dot"></span>Planned</span></div>
-            <div class="gold-rule"></div>
-            <div class="info-list">
-                <div class="info-row"><span class="info-label">Date</span><span class="info-val">${w.date}</span></div>
-                <div class="info-row"><span class="info-label">Budget</span><span class="info-val">${w.budget ? '$'+Number(w.budget).toLocaleString() : 'Not set'}</span></div>
-                ${(() => {
-                    const s = calcBudgetSpent();
-                    if (!s.predefined && !s.total) return '';
-                    const pct      = s.predefined > 0 ? Math.min((s.total / s.predefined) * 100, 100) : 0;
-                    const spentFmt = '$' + Number(s.total).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
-                    const barColor = s.exceeded ? 'var(--rose)' : pct > 80 ? '#f59e0b' : '#22c55e';
-                    return `<div class="info-row" style="flex-direction:column;align-items:flex-start;gap:6px;padding-bottom:4px;">
-                        <div style="display:flex;justify-content:space-between;width:100%;">
-                            <span class="info-label">Budget Spent</span>
-                            <span class="info-val" style="color:${barColor};font-weight:700;">${spentFmt}${s.exceeded ? ' ⚠️ Over!' : ''}</span>
-                        </div>
-                        ${s.predefined > 0 ? `<div style="width:100%;background:#e5e7eb;border-radius:999px;height:7px;overflow:hidden;">
-                            <div style="height:100%;width:${pct.toFixed(1)}%;background:${barColor};border-radius:999px;"></div>
-                        </div>
-                        <div style="font-size:11px;color:var(--text-mid);">
-                            ${pct.toFixed(0)}% of $${Number(s.predefined).toLocaleString()} used
-                            ${s.exceeded ? ' — over by $'+Number(s.over_by).toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:2}) : ''}
-                            &nbsp;·&nbsp; Venue $${Number(s.venueCost).toLocaleString()} · Band $${Number(s.bandCost).toLocaleString()} · Photo $${Number(s.photoCost).toLocaleString()}
-                        </div>` : ''}
-                    </div>`;
-                })()}
-                <div class="info-row"><span class="info-label">Notes</span><span class="info-val" style="font-size:12px;color:var(--text-mid);">${w.notes || '—'}</span></div>
-                <div class="info-row"><span class="info-label">Photographers</span><span class="info-val">${PHOTOGRAPHER_BOOKINGS.length} booked</span></div>
-                <div class="info-row"><span class="info-label">Bands</span><span class="info-val">${BAND_BOOKINGS.length} booked</span></div>
-            </div>
-            <div style="display:flex;gap:8px;margin-top:18px;flex-wrap:wrap;">
-                <button class="btn btn-primary" onclick="navigate('#/event')">+ Add Event</button>
-                <button class="btn btn-gold" onclick="navigate('#/venue')">Book Venue</button>
-                <button class="btn" onclick="navigate('#/guests')">View Guests</button>
-            </div>
-        </div>
-        <div class="card">
-            <div class="card-header"><h3>Upcoming Events</h3><button class="btn btn-ghost btn-sm" onclick="navigate('#/event')">Manage all</button></div>
-            <div class="table-wrap" style="max-height:300px;">
-                <table><thead><tr><th>Type</th><th>Date</th><th>Time</th><th>Status</th></tr></thead>
-                <tbody>
-                ${EVENTS.length === 0
-                    ? `<tr class="no-data-row"><td colspan="4">No events yet.</td></tr>`
-                    : EVENTS.map(e=>`<tr>
-                        <td style="font-weight:500;">${e.event_type}</td>
-                        <td>${e.date}</td>
-                        <td><span class="badge badge-info">${e.start_time}–${e.end_time}</span></td>
-                        <td><span class="badge ${e.status==='scheduled'?'badge-success':e.status==='cancelled'?'badge-danger':'badge-warn'}">${e.status}</span></td>
-                    </tr>`).join('')}
-                </tbody></table>
-            </div>
-        </div>
-        <div class="card dash-full">
-            <div class="card-header"><h3>Quick Actions</h3></div>
-            <div style="display:flex;gap:10px;flex-wrap:wrap;">
-                <button class="btn btn-primary" onclick="navigate('#/event')">+ New Event</button>
-                <button class="btn btn-gold" onclick="navigate('#/venue')">+ Book Venue</button>
-                <button class="btn" onclick="navigate('#/band')">+ Book Band</button>
-                <button class="btn" onclick="navigate('#/photographer')">+ Book Photographer</button>
-                <button class="btn" onclick="navigate('#/guests')">+ Add Guest</button>
-                <button class="btn" onclick="navigate('#/seating')">🪑 Manage Seating</button>
-                <button class="btn" onclick="navigate('#/registrar')">+ Book Registrar</button>
-            </div>
-        </div>
-    </div>`;
-}
-
-// =============================================================
-//  EVENTS PAGE
-// =============================================================
-function renderEventPage(container){
-    if(!CURRENT_WEDDING){ renderNoWedding(container); return; }
-    const wrap = document.createElement('div');
-    wrap.innerHTML = `<div class="card"><div class="card-header"><h3>Events</h3><div class="card-actions"><button class="btn btn-primary" id="btnAddEvent">+ Add Event</button></div></div><div id="evTableContainer"></div></div>`;
-    container.appendChild(wrap);
-    wrap.querySelector('#btnAddEvent').addEventListener('click', openEventModal);
-
-    buildDataTable({
-        container: wrap.querySelector('#evTableContainer'),
-        columns: [
-            { header:'Event Type', accessor:'event_type' },
-            { header:'Date', accessor:'date' },
-            { header:'Time', accessor:'time', sortable:false },
-            { header:'Status', accessor:'status', render: v=>`<span class="badge ${v==='scheduled'?'badge-success':v==='cancelled'?'badge-danger':'badge-warn'}">${v}</span>` }
-        ],
-        rows: EVENTS.map(e=>({ id:e.event_id, event_type:e.event_type, date:e.date, time:`${e.start_time}–${e.end_time}`, status:e.status })),
-        onEdit: row => editEventModal(row.id),
-        onDelete: async row => {
-            const res = await api('DELETE',`/api/events/${row.id}`);
-            if(res && res.ok){ showToast('Event deleted.',''); await reload(); }
-            else showToast('Could not delete event.','danger');
-        }
-    });
-}
-
-function openEventModal(){
-    const w = CURRENT_WEDDING;
-    showModal('Add Event',
-        `<div class="form-grid">
-            <div class="form-group"><label class="form-label">Event Type</label><input id="ev_type" placeholder="e.g. Church Ceremony"/></div>
-            <div class="form-group"><label class="form-label">Date</label><input id="ev_date" type="date" value="${w.date}"/></div>
-            <div class="form-group"><label class="form-label">Start Time</label><input id="ev_start" type="time" value="12:00"/></div>
-            <div class="form-group"><label class="form-label">End Time</label><input id="ev_end" type="time" value="14:00"/></div>
-            <div class="form-group"><label class="form-label">Status</label>
-                <select id="ev_status"><option value="scheduled">Scheduled</option><option value="completed">Completed</option><option value="cancelled">Cancelled</option></select>
-            </div>
-        </div>`,
-        async () => {
-            const type = document.getElementById('ev_type').value.trim();
-            if(!type){ showToast('Event type is required.','danger'); return; }
-            const res = await api('POST',`/api/weddings/${CURRENT_WEDDING.wedding_id}/events`,{
-                event_type: type,
-                date: document.getElementById('ev_date').value,
-                start_time: document.getElementById('ev_start').value,
-                end_time: document.getElementById('ev_end').value,
-                status: document.getElementById('ev_status').value
-            });
-            if(res && res.ok){ closeModal(); showToast('Event added.','success'); await reload(); }
-            else { const d=await res.json(); showToast(d.error||'Error adding event.','danger'); }
-        }
-    );
-}
-
-function editEventModal(id){
-    const e = EVENTS.find(x=>x.event_id===id); if(!e) return;
-    showModal('Edit Event',
-        `<div class="form-grid">
-            <div class="form-group"><label class="form-label">Event Type</label><input id="ev_type" value="${e.event_type}"/></div>
-            <div class="form-group"><label class="form-label">Date</label><input id="ev_date" type="date" value="${e.date}"/></div>
-            <div class="form-group"><label class="form-label">Start Time</label><input id="ev_start" type="time" value="${e.start_time}"/></div>
-            <div class="form-group"><label class="form-label">End Time</label><input id="ev_end" type="time" value="${e.end_time}"/></div>
-            <div class="form-group"><label class="form-label">Status</label>
-                <select id="ev_status">
-                    <option ${e.status==='scheduled'?'selected':''} value="scheduled">Scheduled</option>
-                    <option ${e.status==='completed'?'selected':''} value="completed">Completed</option>
-                    <option ${e.status==='cancelled'?'selected':''} value="cancelled">Cancelled</option>
-                </select>
-            </div>
-        </div>`,
-        async () => {
-            const res = await api('PUT',`/api/events/${id}`,{
-                event_type: document.getElementById('ev_type').value,
-                date: document.getElementById('ev_date').value,
-                start_time: document.getElementById('ev_start').value,
-                end_time: document.getElementById('ev_end').value,
-                status: document.getElementById('ev_status').value
-            });
-            if(res && res.ok){ closeModal(); showToast('Event updated.','success'); await reload(); }
-            else showToast('Error updating event.','danger');
-        }
-    );
-}
-
-// =============================================================
-//  GUESTS PAGE
-// =============================================================
-function renderGuestsPage(container){
-    if(!CURRENT_WEDDING){ renderNoWedding(container); return; }
-    const wrap = document.createElement('div');
-    wrap.innerHTML = `<div class="card"><div class="card-header"><h3>Guest List</h3><div class="card-actions"><button class="btn btn-primary" id="btnAddGuest">+ Add Guest</button><button class="btn" id="btnUploadGuests">Upload CSV</button></div></div><div id="guestTableContainer"></div></div>`;
-    container.appendChild(wrap);
-    wrap.querySelector('#btnAddGuest').addEventListener('click', openAddGuestModal);
-    wrap.querySelector('#btnUploadGuests').addEventListener('click', () => {
-        if(!CURRENT_WEDDING){ showToast('Select or create a wedding first.','danger'); return; }
-        const input = document.createElement('input');
-        input.type = 'file';
-        input.accept = '.csv,text/csv';
-        input.addEventListener('change', async (e) => {
-            const file = e.target.files && e.target.files[0];
-            if(!file) return;
-            const text = await file.text();
-            const rows = parseCSV(text);
-            if(rows.length === 0){ showToast('No guests found in CSV.','danger'); return; }
-
-            const previewRows = rows.slice(0,200).map(r=>`<tr><td>${escapeHtml(r.first_name||'')}</td><td>${escapeHtml(r.last_name||'')}</td><td>${escapeHtml(r.email||'')}</td><td>${escapeHtml(r.role||'')}</td></tr>`).join('');
-            const body = `<div style="max-height:320px;overflow:auto;"><table style="width:100%;border-collapse:collapse;"><thead><tr><th style="text-align:left;padding:6px">First</th><th style="text-align:left;padding:6px">Last</th><th style="text-align:left;padding:6px">Email</th><th style="text-align:left;padding:6px">Role</th></tr></thead><tbody>${previewRows}</tbody></table></div><div style="margin-top:12px;"><label><input type="checkbox" id="csvSendEmails"/> Send invitation emails</label></div><p class="muted" style="margin-top:10px;">${rows.length} guests parsed. Showing first ${Math.min(rows.length,200)} rows.</p>`;
-
-            showModal('Import Guests from CSV', body, async () => {
-                const sendEmails = document.getElementById('csvSendEmails').checked;
-                const couple = getCoupleNames();
-                const res = await api('POST', `/api/weddings/${CURRENT_WEDDING.wedding_id}/guests/bulk`, { guests: rows, sendEmails, bride_name: couple.bride_name, groom_name: couple.groom_name });
-                if(res && res.ok){ const d = await res.json(); closeModal(); showToast(`${d.inserted} guests imported.`,'success'); await reload(); }
-                else { const d = await res.json().catch(()=>null); showToast((d&&d.error) || 'Error importing guests.','danger'); }
-            });
-        });
-        input.click();
-    });
-
-    // Build table after ensuring we have up-to-date RSVP data for this wedding
-    (async () => {
-        // Fetch fresh RSVPs for this wedding to ensure the UI reflects guest-submitted responses
-        try {
-            const r = await api('GET', `/api/weddings/${CURRENT_WEDDING.wedding_id}/rsvp`);
-            if (r && r.ok) RSVPS = await r.json();
-        } catch (e) { /* ignore, we'll use cached RSVPS if available */ }
-
-        const guestRsvpMap = {};
-        if (EVENTS.length > 0) {
-            const firstEvent = EVENTS[0];
-            RSVPS.forEach(rv => {
-                if (String(rv.event_id) === String(firstEvent.event_id)) guestRsvpMap[String(rv.guest_id)] = rv.status;
-            });
-        }
-
-        buildDataTable({
-            container: wrap.querySelector('#guestTableContainer'),
-            columns: [
-                { header:'First Name', accessor:'first_name' },
-                { header:'Last Name', accessor:'last_name' },
-                { header:'Email', accessor:'email' },
-                { header:'Status', accessor:'status', render: v => {
-                    const color = v === 'accepted' ? 'badge-success' : v === 'declined' ? 'badge-danger' : v === 'invited' ? 'badge-warn' : 'badge-purple';
-                    return `<span class="badge ${color}">${v || 'pending'}</span>`;
-                }},
-                { header:'Invite', accessor:'email', sortable:false, render: (v,row) =>
-                    v ? `<span class="badge badge-success">✉ Invite sent</span>` : `<span class="badge badge-warn">No email</span>`
-                }
-            ],
-            rows: GUESTS.map(g=>({ id:g.guest_id, first_name:g.first_name, last_name:g.last_name, email:g.email||'', status: guestRsvpMap[String(g.guest_id)] || 'pending' })),
-            onEdit: row => editGuestModal(row.id),
-            onDelete: async row => {
-                const res = await api('DELETE',`/api/guests/${row.id}`);
-                if(res && res.ok){ showToast('Guest removed.',''); await reload(); }
-                else showToast('Could not remove guest.','danger');
-            }
-        });
-    })();
-}
-
-// CSV helpers for guest import
-function splitCSVLine(line){
-    const out = [];
-    let cur = '';
-    let inQuotes = false;
-    for(let i=0;i<line.length;i++){
-        const ch = line[i];
-        if(ch === '"'){
-            if(inQuotes && line[i+1] === '"'){ cur += '"'; i++; }
-            else inQuotes = !inQuotes;
-        } else if(ch === ',' && !inQuotes){ out.push(cur); cur = ''; }
-        else cur += ch;
-    }
-    out.push(cur);
-    return out.map(s=>s.trim());
-}
-
-function parseCSV(text){
-    if(!text) return [];
-    const lines = text.split(/\r?\n/).map(l=>l.trim()).filter(l=>l.length>0);
-    if(lines.length === 0) return [];
-    const first = splitCSVLine(lines[0]).map(h=>h.toLowerCase());
-    const hasHeader = first.some(h=>['first','first_name','first name','last','last_name','email','role'].includes(h));
-    const rows = [];
-    const start = hasHeader ? 1 : 0;
-    let headers = first;
-    if(!hasHeader) headers = ['first_name','last_name','email','role'];
-    for(let i=start;i<lines.length;i++){
-        const cols = splitCSVLine(lines[i]);
-        const obj = {};
-        for(let j=0;j<headers.length;j++){
-            const key = headers[j] ? headers[j].toLowerCase().replace(/ /g,'_') : `col${j}`;
-            obj[key] = cols[j] ? cols[j].trim().replace(/^"|"$/g,'') : '';
-        }
-        const out = {
-            first_name: obj.first_name || obj.first || '',
-            last_name: obj.last_name || obj.last || '',
-            email: obj.email || '',
-            role: obj.role || ''
-        };
-        if(out.first_name || out.last_name) rows.push(out);
-    }
-    return rows;
-}
-
-function escapeHtml(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
-
-function openAddGuestModal(){
-    showModal('Add Guest',
-        `<div class="form-grid">
-            <div class="form-group"><label class="form-label">First Name</label><input id="gFirst" placeholder="First name"/></div>
-            <div class="form-group"><label class="form-label">Last Name</label><input id="gLast" placeholder="Last name"/></div>
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Email (invitation will be sent)</label><input id="gEmail" type="email" placeholder="guest@email.com"/></div>
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Role</label><select id="gRole"><option>Guest</option><option>Best Man</option><option>Maid of Honor</option><option>Bridesmaid</option><option>Groomsman</option><option>Family</option><option>VIP</option></select></div>
-        </div>`,
-        async () => {
-            const first = document.getElementById('gFirst').value.trim();
-            if(!first){ showToast('First name is required.','danger'); return; }
-            const couple = getCoupleNames();
-            const res = await api('POST',`/api/weddings/${CURRENT_WEDDING.wedding_id}/guests`,{
-                first_name: first,
-                last_name: document.getElementById('gLast').value.trim(),
-                email: document.getElementById('gEmail').value.trim(),
-                role: document.getElementById('gRole').value,
-                bride_name: couple.bride_name,
-                groom_name: couple.groom_name
-            });
-            if(res && res.ok){
-                const d = await res.json();
-                closeModal();
-                showToast(d.emailSent ? 'Guest added & invitation email sent! 📧' : 'Guest added (no email provided).','success');
-                await reload();
-                checkBudgetAndAlert();
-            } else showToast('Could not add guest.','danger');
-        }
-    );
-}
-
-function editGuestModal(id){
-    const g = GUESTS.find(x=>x.guest_id===id); if(!g) return;
-    showModal('Edit Guest',
-        `<div class="form-grid">
-            <div class="form-group"><label class="form-label">First Name</label><input id="gFirst" value="${g.first_name}"/></div>
-            <div class="form-group"><label class="form-label">Last Name</label><input id="gLast" value="${g.last_name}"/></div>
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Email</label><input id="gEmail" value="${g.email||''}"/></div>
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Role</label><select id="gRole"><option ${g.role==='Guest'?'selected':''}>Guest</option><option ${g.role==='Best Man'?'selected':''}>Best Man</option><option ${g.role==='Maid of Honor'?'selected':''}>Maid of Honor</option><option ${g.role==='Bridesmaid'?'selected':''}>Bridesmaid</option><option ${g.role==='Groomsman'?'selected':''}>Groomsman</option><option ${g.role==='Family'?'selected':''}>Family</option><option ${g.role==='VIP'?'selected':''}>VIP</option></select></div>
-        </div>`,
-        async () => {
-            const res = await api('PUT',`/api/guests/${id}`,{
-                first_name: document.getElementById('gFirst').value,
-                last_name: document.getElementById('gLast').value,
-                email: document.getElementById('gEmail').value,
-                role: document.getElementById('gRole').value
-            });
-            if(res && res.ok){ closeModal(); showToast('Guest updated.','success'); await reload(); }
-            else showToast('Error updating guest.','danger');
-        }
-    );
-}
-
-// =============================================================
-//  SEATING PAGE  (NEW — UC0011)
-// =============================================================
-function renderSeatingPage(container){
-    if(!CURRENT_WEDDING){ renderNoWedding(container); return; }
-
-    const wrap = document.createElement('div');
-
-    // Build event selector
-    const eventOptions = EVENTS.map(e=>`<option value="${e.event_id}">${e.event_type} (${e.date})</option>`).join('');
-
-    wrap.innerHTML = `
-    <div class="card" style="margin-bottom:18px;">
-        <div class="card-header">
-            <h3>🪑 Table Seating</h3>
-            <div class="card-actions">
-                <select id="seatEventSel" style="width:220px;">
-                    <option value="">— Select Event —</option>
-                    ${eventOptions}
-                </select>
-                <button class="btn btn-primary" id="btnAssignSeat">+ Assign Guest</button>
-            </div>
-        </div>
-        <div class="gold-rule"></div>
-        <p class="muted" style="margin-bottom:16px;">Select an event to view and manage table assignments. Tables are created dynamically based on assigned guests.</p>
-        <div id="seatingBoard"></div>
-    </div>
-    <div class="card">
-        <div class="card-header"><h3>All Assignments</h3></div>
-        <div id="seatingTableContainer"></div>
-    </div>`;
-
-    container.appendChild(wrap);
-
-    const sel = wrap.querySelector('#seatEventSel');
-    sel.addEventListener('change', () => renderSeatingBoard(wrap, sel.value));
-    wrap.querySelector('#btnAssignSeat').addEventListener('click', () => openAssignSeatModal(sel.value));
-
-    // Full table view
-    buildDataTable({
-        container: wrap.querySelector('#seatingTableContainer'),
-        columns: [
-            { header:'Guest', accessor:'guest_name' },
-            { header:'Event', accessor:'event_type' },
-            { header:'Table #', accessor:'table_number', render: v => v ? `<span class="badge badge-gold">Table ${v}</span>` : '<span class="badge badge-warn">Unassigned</span>' },
-            { header:'Role', accessor:'role', render: v => `<span class="badge badge-purple">${v}</span>` },
-            { header:'Status', accessor:'status', render: v => `<span class="badge ${v==='attending'?'badge-success':'badge-danger'}">${v}</span>` }
-        ],
-        rows: ATTENDANCE.map(a=>({
-            id: a.attendance_id,
-            guest_name: `${a.first_name} ${a.last_name}`,
-            event_type: a.event_type,
-            table_number: a.table_number,
-            role: a.role,
-            status: a.status
-        })),
-        onEdit: row => openEditSeatModal(row.id),
-        onDelete: async row => {
-            const res = await api('DELETE',`/api/attendance/${row.id}`);
-            if(res && res.ok){ showToast('Assignment removed.',''); await reload(); }
-            else showToast('Error removing assignment.','danger');
-        }
-    });
-}
-
-function renderSeatingBoard(wrap, eventId){
-    const board = wrap.querySelector('#seatingBoard');
-    if(!eventId){ board.innerHTML = '<p class="muted">Select an event above to see the seating layout.</p>'; return; }
-
-    const eventAtt = ATTENDANCE.filter(a => String(a.event_id) === String(eventId));
-    const assigned = eventAtt.filter(a => a.table_number);
-    const unassigned = eventAtt.filter(a => !a.table_number);
-
-    // Group by table
-    const tables = {};
-    assigned.forEach(a => {
-        if(!tables[a.table_number]) tables[a.table_number] = [];
-        tables[a.table_number].push(a);
-    });
-
-    const tableNums = Object.keys(tables).map(Number).sort((a,b)=>a-b);
-
-    let html = '';
-
-    if(tableNums.length === 0 && unassigned.length === 0){
-        html = '<p class="muted">No guests assigned to this event yet. Click "+ Assign Guest" to start.</p>';
-    } else {
-        if(tableNums.length > 0){
-            html += `<h4 style="font-family:'Cormorant Garamond',serif;font-size:17px;margin-bottom:12px;color:var(--text-dark);">Seating Layout</h4>
-            <div class="seating-grid">
-            ${tableNums.map(tnum => `
-                <div class="table-card-seat">
-                    <div class="table-title">🍽 Table ${tnum} <span style="font-size:12px;font-weight:400;color:var(--text-light);">(${tables[tnum].length} guests)</span></div>
-                    ${tables[tnum].map(a=>`
-                        <div class="seat-guest">
-                            <span>${a.first_name} ${a.last_name}</span>
-                            <span class="seat-role">${a.role}</span>
-                        </div>`).join('')}
-                </div>`).join('')}
-            </div>`;
-        }
-        if(unassigned.length > 0){
-            html += `<h4 style="font-family:'Cormorant Garamond',serif;font-size:17px;margin:20px 0 10px;color:var(--amber);">⚠ Unassigned Guests (${unassigned.length})</h4>
-            <div class="unassigned-list">
-            ${unassigned.map(a=>`
-                <div class="unassigned-item">
-                    <span>${a.first_name} ${a.last_name} <span style="color:var(--text-light);font-size:12px;">(${a.role})</span></span>
-                    <button class="btn btn-sm btn-gold" onclick="openEditSeatModal(${a.attendance_id})">Assign Table</button>
-                </div>`).join('')}
-            </div>`;
-        }
-    }
-
-    board.innerHTML = html;
-}
-
-function openAssignSeatModal(preselectedEventId){
-    const eventOptions = EVENTS.map(e=>`<option value="${e.event_id}" ${String(e.event_id)===String(preselectedEventId)?'selected':''}>${e.event_type} (${e.date})</option>`).join('');
-    const guestOptions = GUESTS.map(g=>`<option value="${g.guest_id}">${g.first_name} ${g.last_name}</option>`).join('');
-
-    showModal('Assign Guest to Table',
-        `<div class="form-grid">
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Event</label><select id="attEvent">${eventOptions}</select></div>
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Guest</label><select id="attGuest">${guestOptions}</select></div>
-            <div class="form-group"><label class="form-label">Table Number</label><input id="attTable" type="number" min="1" placeholder="e.g. 5"/></div>
-            <div class="form-group"><label class="form-label">Role</label>
-                <select id="attRole">
-                    <option value="Guest">Guest</option>
-                    <option value="Best Man">Best Man</option>
-                    <option value="Maid of Honor">Maid of Honor</option>
-                    <option value="Bridesmaid">Bridesmaid</option>
-                    <option value="Groomsman">Groomsman</option>
-                    <option value="Family">Family</option>
-                    <option value="VIP">VIP</option>
-                </select>
-            </div>
-            <div class="form-group" style="grid-column:1/-1;"><p class="muted" style="margin:0;">Set the attendance status manually.</p></div>
-        </div>`,
-        async () => {
-            const res = await api('POST','/api/attendance',{
-                event_id: document.getElementById('attEvent').value,
-                guest_id: document.getElementById('attGuest').value,
-                table_number: document.getElementById('attTable').value || null,
-                role: document.getElementById('attRole').value
-            });
-            if(res && res.ok){ closeModal(); showToast('Guest assigned to table!','success'); await reload(); }
-            else {
-                const d=await res.json();
-                const msg = d.error||'Error assigning guest.';
-                showToast(msg,'danger');
-                alert(msg);
-            }
-        }
-    );
-}
-
-function openEditSeatModal(id){
-    const a = ATTENDANCE.find(x=>x.attendance_id===id); if(!a) return;
-    showModal('Edit Seating Assignment',
-        `<div class="form-grid">
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Guest</label><input disabled value="${a.first_name} ${a.last_name}"/></div>
-            <div class="form-group"><label class="form-label">Table Number</label><input id="attTable" type="number" min="1" value="${a.table_number||''}"/></div>
-            <div class="form-group"><label class="form-label">Role</label>
-                <select id="attRole">
-                    ${['Guest','Best Man','Maid of Honor','Bridesmaid','Groomsman','Family','VIP'].map(r=>`<option ${a.role===r?'selected':''}>${r}</option>`).join('')}
-                </select>
-            </div>
-            <div class="form-group"><label class="form-label">Status</label>
-                <select id="attStatus">
-                    <option ${a.status==='attending'?'selected':''} value="attending">Attending</option>
-                    <option ${a.status==='not_attending'?'selected':''} value="not_attending">Not Attending</option>
-                </select>
-            </div>
-            <div class="form-group" style="grid-column:1/-1;"><p class="muted" style="margin:0;">Set the attendance status manually.</p></div>
-        </div>`,
-        async () => {
-            const res = await api('PUT',`/api/attendance/${id}`,{
-                table_number: document.getElementById('attTable').value || null,
-                role: document.getElementById('attRole').value,
-                status: document.getElementById('attStatus').value
-            });
-            if(res && res.ok){ closeModal(); showToast('Seating updated!','success'); await reload(); }
-            else {
-                const d=await res.json().catch(()=>({}));
-                const msg = d.error||'Error updating seating.';
-                showToast(msg,'danger');
-                alert(msg);
-            }
-        }
-    );
-}
-
-// =============================================================
-//  VENUE PAGE
-// =============================================================
-function renderVenuePage(container){
-    if(!CURRENT_WEDDING){ renderNoWedding(container); return; }
-    const wrap = document.createElement('div');
-    wrap.innerHTML = `
-    <div class="card" style="margin-bottom:18px;">
-        <div class="card-header"><h3>Venues</h3><div class="card-actions"><button class="btn btn-primary" id="btnBookVenue">+ Book Venue</button></div></div>
-        <div id="venueTableContainer"></div>
-    </div>
-    <div class="card">
-        <div class="card-header"><h3>Venue Bookings</h3></div>
-        <div id="venueBookingTableContainer"></div>
-    </div>`;
-    container.appendChild(wrap);
-    wrap.querySelector('#btnBookVenue').addEventListener('click', openVenueBookingModal);
-
-    buildDataTable({
-        container: wrap.querySelector('#venueTableContainer'),
-        columns: [
-            { header:'Venue Name', accessor:'name' },
-            { header:'City', accessor:'city' },
-            { header:'Capacity', accessor:'capacity' },
-            { header:'Type', accessor:'type_name' },
-            { header:'Price/Guest', accessor:'price_per_guest', render: v=>`<strong>$${v}</strong>` }
-        ],
-        rows: VENUES.map(v=>({ id:v.venue_id, name:v.name, city:v.city, capacity:v.capacity, type_name:v.type_name, price_per_guest:v.price_per_guest })),
-        onEdit: row => showToast(`Viewing ${row.name}`,'')
-    });
-
-    buildDataTable({
-        container: wrap.querySelector('#venueBookingTableContainer'),
-        columns: [
-            { header:'Venue', accessor:'venue_name' },
-            { header:'Date', accessor:'date' },
-            { header:'Time', accessor:'time', sortable:false },
-            { header:'Price', accessor:'price', render: v=>`$${Number(v).toLocaleString()}` },
-            { header:'Status', accessor:'status', render: v=>`<span class="badge badge-success">${v}</span>` }
-        ],
-        rows: VENUE_BOOKINGS.map(b=>({ id:b.booking_id, venue_name:b.venue_name, date:b.date, time:`${b.start_time}–${b.end_time}`, price:b.price, status:b.status })),
-        onDelete: async row => {
-            const res = await api('DELETE',`/api/venue-bookings/${row.id}`);
-            if(res && res.ok){ showToast('Booking cancelled.',''); await reload(); }
-            else showToast('Could not cancel booking.','danger');
-        }
-    });
-}
-
-function openVenueBookingModal(){
-    const w = CURRENT_WEDDING;
-    const venueOptions = VENUES.map(v=>`<option value="${v.venue_id}">${v.name} — $${v.price_per_guest}/guest</option>`).join('');
-    showModal('Book a Venue',
-        `<div class="form-grid">
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Venue</label><select id="venueSelect">${venueOptions}</select></div>
-            <div class="form-group"><label class="form-label">Date</label><input id="venueDate" type="date" value="${w.date}"/></div>
-            <div class="form-group"><label class="form-label">Price ($)</label><input id="venuePrice" type="number" placeholder="0"/></div>
-            <div class="form-group"><label class="form-label">Start Time</label><input id="venueStart" type="time" value="16:00"/></div>
-            <div class="form-group"><label class="form-label">End Time</label><input id="venueEnd" type="time" value="23:00"/></div>
-        </div>`,
-        async () => {
-            const res = await api('POST',`/api/weddings/${w.wedding_id}/venue-bookings`,{
-                venue_id: document.getElementById('venueSelect').value,
-                date: document.getElementById('venueDate').value,
-                start_time: document.getElementById('venueStart').value,
-                end_time: document.getElementById('venueEnd').value,
-                price: document.getElementById('venuePrice').value || 0,
-                status: 'confirmed'
-            });
-            if(res && res.ok){
-                closeModal();
-                showToast('Venue booked!','success');
-                await reload();
-                checkBudgetAndAlert();
-            }
-            else { const d=await res.json(); showToast(d.error||'Error booking venue.','danger'); }
-        }
-    );
-}
-
-// =============================================================
-//  BAND PAGE
-// =============================================================
-function renderBandPage(container){
-    if(!CURRENT_WEDDING){ renderNoWedding(container); return; }
-    const wrap = document.createElement('div');
-    wrap.innerHTML = `
-    <div class="card" style="margin-bottom:18px;">
-        <div class="card-header"><h3>Bands</h3><div class="card-actions"><button class="btn btn-primary" id="btnBookBand">+ Book Band</button></div></div>
-        <div id="bandTableContainer"></div>
-    </div>
-    <div class="card"><div class="card-header"><h3>Band Bookings</h3></div><div id="bandBookingsContainer"></div></div>`;
-    container.appendChild(wrap);
-    wrap.querySelector('#btnBookBand').addEventListener('click', openBandBookingModal);
-
-    buildDataTable({
-        container: wrap.querySelector('#bandTableContainer'),
-        columns: [
-            { header:'Band Name', accessor:'band_name' },
-            { header:'Genre', accessor:'genre' },
-            { header:'Equipment', accessor:'equipment', sortable:false },
-            { header:'Price/Hour', accessor:'price_per_hour', render: v=>`<strong>$${v}</strong>` }
-        ],
-        rows: BANDS.map(b=>({ id:b.band_id, band_name:b.band_name, genre:b.genre, equipment:b.equipment||'—', price_per_hour:b.price_per_hour })),
-        onEdit: row => showToast(`Viewing ${row.band_name}`,'')
-    });
-
-    buildDataTable({
-        container: wrap.querySelector('#bandBookingsContainer'),
-        columns: [
-            { header:'Band', accessor:'band_name' },
-            { header:'Date', accessor:'date' },
-            { header:'Time', accessor:'time', sortable:false },
-            { header:'Status', accessor:'status', render: v=>`<span class="badge badge-success">${v}</span>` }
-        ],
-        rows: BAND_BOOKINGS.map(b=>({ id:b.booking_id, band_name:b.band_name, date:b.date, time:`${b.start_time}–${b.end_time}`, status:b.status })),
-        onDelete: async row => {
-            const res = await api('DELETE',`/api/band-bookings/${row.id}`);
-            if(res && res.ok){ showToast('Band booking removed.',''); await reload(); }
-            else showToast('Error removing booking.','danger');
-        }
-    });
-}
-
-function openBandBookingModal(){
-    const w = CURRENT_WEDDING;
-    const opts = BANDS.map(b=>`<option value="${b.band_id}">${b.band_name} — $${b.price_per_hour}/hr</option>`).join('');
-    showModal('Book a Band',
-        `<div class="form-grid">
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Band</label><select id="bandSel">${opts}</select></div>
-            <div class="form-group"><label class="form-label">Date</label><input id="bandDate" type="date" value="${w.date}"/></div>
-            <div class="form-group"><label class="form-label">Start Time</label><input id="bandStart" type="time" value="19:00"/></div>
-            <div class="form-group"><label class="form-label">End Time</label><input id="bandEnd" type="time" value="23:00"/></div>
-        </div>`,
-        async () => {
-            const res = await api('POST',`/api/weddings/${w.wedding_id}/band-bookings`,{
-                band_id: document.getElementById('bandSel').value,
-                date: document.getElementById('bandDate').value,
-                start_time: document.getElementById('bandStart').value,
-                end_time: document.getElementById('bandEnd').value,
-                status: 'confirmed'
-            });
-            if(res && res.ok){
-                closeModal();
-                showToast('Band booked!','success');
-                await reload();
-                checkBudgetAndAlert();
-            }
-            else { const d=await res.json(); showToast(d.error||'Error booking band.','danger'); }
-        }
-    );
-}
-
-// =============================================================
-//  PHOTOGRAPHER PAGE
-// =============================================================
-function renderPhotographerPage(container){
-    if(!CURRENT_WEDDING){ renderNoWedding(container); return; }
-    const wrap = document.createElement('div');
-    wrap.innerHTML = `
-    <div class="card" style="margin-bottom:18px;">
-        <div class="card-header"><h3>Photographers</h3><div class="card-actions"><button class="btn btn-primary" id="btnBookPhotog">+ Book Photographer</button></div></div>
-        <div id="photogTableContainer"></div>
-    </div>
-    <div class="card"><div class="card-header"><h3>Photographer Bookings</h3></div><div id="photogBookingContainer"></div></div>`;
-    container.appendChild(wrap);
-    wrap.querySelector('#btnBookPhotog').addEventListener('click', openPhotographerBookingModal);
-
-    buildDataTable({
-        container: wrap.querySelector('#photogTableContainer'),
-        columns: [
-            { header:'Name', accessor:'name' },
-            { header:'Email', accessor:'email', sortable:false },
-            { header:'Phone', accessor:'phone', sortable:false },
-            { header:'Price/Hour', accessor:'price_per_hour', render: v=>`<strong>$${v}</strong>` }
-        ],
-        rows: PHOTOGRAPHERS.map(p=>({ id:p.photographer_id, name:p.name, email:p.email, phone:p.phone_number, price_per_hour:p.price_per_hour })),
-        onEdit: row => showToast(`Viewing ${row.name}`,'')
-    });
-
-    buildDataTable({
-        container: wrap.querySelector('#photogBookingContainer'),
-        columns: [
-            { header:'Photographer', accessor:'name' },
-            { header:'Date', accessor:'date' },
-            { header:'Time', accessor:'time', sortable:false },
-            { header:'Status', accessor:'status', render: v=>`<span class="badge badge-success">${v}</span>` }
-        ],
-        rows: PHOTOGRAPHER_BOOKINGS.map(b=>({ id:b.booking_id, name:b.photographer_name, date:b.date, time:`${b.start_time}–${b.end_time}`, status:b.status })),
-        onDelete: async row => {
-            const res = await api('DELETE',`/api/photographer-bookings/${row.id}`);
-            if(res && res.ok){ showToast('Booking removed.',''); await reload(); }
-            else showToast('Error removing booking.','danger');
-        }
-    });
-}
-
-function openPhotographerBookingModal(){
-    const w = CURRENT_WEDDING;
-    const opts = PHOTOGRAPHERS.map(p=>`<option value="${p.photographer_id}">${p.name} — $${p.price_per_hour}/hr</option>`).join('');
-    showModal('Book a Photographer',
-        `<div class="form-grid">
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Photographer</label><select id="photogSel">${opts}</select></div>
-            <div class="form-group"><label class="form-label">Date</label><input id="photogDate" type="date" value="${w.date}"/></div>
-            <div class="form-group"><label class="form-label">Start Time</label><input id="photogStart" type="time" value="14:00"/></div>
-            <div class="form-group"><label class="form-label">End Time</label><input id="photogEnd" type="time" value="22:00"/></div>
-        </div>`,
-        async () => {
-            const res = await api('POST',`/api/weddings/${w.wedding_id}/photographer-bookings`,{
-                photographer_id: document.getElementById('photogSel').value,
-                date: document.getElementById('photogDate').value,
-                start_time: document.getElementById('photogStart').value,
-                end_time: document.getElementById('photogEnd').value,
-                status: 'confirmed'
-            });
-            if(res && res.ok){
-                closeModal();
-                showToast('Photographer booked!','success');
-                await reload();
-                checkBudgetAndAlert();
-            }
-            else { const d=await res.json(); showToast(d.error||'Error booking photographer.','danger'); }
-        }
-    );
-}
-
-// =============================================================
-//  CHURCH PAGE
-// =============================================================
-function renderChurchPage(container){
-    if(!CURRENT_WEDDING){ renderNoWedding(container); return; }
-    const wrap = document.createElement('div');
-    wrap.innerHTML = `
-    <div class="card" style="margin-bottom:18px;">
-        <div class="card-header"><h3>Churches</h3><div class="card-actions"><button class="btn btn-primary" id="btnBookChurch">+ Book Church</button></div></div>
-        <div id="churchTableContainer"></div>
-    </div>
-    <div class="card"><div class="card-header"><h3>Church Bookings</h3></div><div id="churchBookingContainer"></div></div>`;
-    container.appendChild(wrap);
-    wrap.querySelector('#btnBookChurch').addEventListener('click', openChurchBookingModal);
-
-    buildDataTable({
-        container: wrap.querySelector('#churchTableContainer'),
-        columns: [
-            { header:'Church', accessor:'name' },
-            { header:'Location', accessor:'location' },
-            { header:'Contact', accessor:'contact', sortable:false },
-            { header:'Priest', accessor:'priest_name', sortable:false }
-        ],
-        rows: CHURCHES.map(c=>({ id:c.church_id, name:c.name, location:c.location, contact:c.contact, priest_name:c.priest_name||'—' })),
-        onEdit: row => showToast(`Viewing ${row.name}`,'')
-    });
-
-    buildDataTable({
-        container: wrap.querySelector('#churchBookingContainer'),
-        columns: [
-            { header:'Church', accessor:'church_name' },
-            { header:'Date', accessor:'date' },
-            { header:'Time', accessor:'time', sortable:false },
-            { header:'Status', accessor:'status', render: v=>`<span class="badge badge-success">${v}</span>` },
-            { header:'Actions', accessor:'id', sortable:false, render: id=>`<button class="btn btn-sm btn-danger" onclick="deleteChurchBooking(${id})">Delete</button>` }
-        ],
-        rows: CHURCH_BOOKINGS.map(b=>({ id:b.church_booking_id, church_name: b.church_name||'—', date:b.date, time:`${b.start_time||'—'}–${b.end_time||'—'}`, status:b.status })),
-        onEdit: undefined
-    });
-}
-
-// =============================================================
-//  REGISTRAR PAGE
-// =============================================================
-function renderRegistrarPage(container){
-    if(!CURRENT_WEDDING){ renderNoWedding(container); return; }
-    const wrap = document.createElement('div');
-    wrap.innerHTML = `
-    <div class="card" style="margin-bottom:18px;">
-        <div class="card-header"><h3>Registrars</h3><div class="card-actions"><button class="btn btn-primary" id="btnBookReg">+ Book Registrar</button></div></div>
-        <div id="regTableContainer"></div>
-    </div>
-    <div class="card"><div class="card-header"><h3>Registrar Bookings</h3></div><div id="regBookingContainer"></div></div>`;
-    container.appendChild(wrap);
-    wrap.querySelector('#btnBookReg').addEventListener('click', openRegistrarBookingModal);
-
-    buildDataTable({
-        container: wrap.querySelector('#regTableContainer'),
-        columns: [
-            { header:'Name', accessor:'name' },
-            { header:'Location', accessor:'location' },
-            { header:'Working Hours', accessor:'working_hours', sortable:false },
-            { header:'Contact', accessor:'contact', sortable:false }
-        ],
-        rows: REGISTRARS.map(r=>({ id:r.registrar_id, name:r.name, location:r.location, working_hours:r.working_hours||'—', contact:r.contact })),
-        onEdit: row => showToast(`Viewing ${row.name}`,'')
-    });
-
-    buildDataTable({
-        container: wrap.querySelector('#regBookingContainer'),
-        columns: [
-            { header:'Registrar', accessor:'registrar_name' },
-            { header:'Date', accessor:'date' },
-            { header:'Time', accessor:'time', sortable:false },
-            { header:'Status', accessor:'status', render: v=>`<span class="badge badge-success">${v}</span>` }
-        ],
-        rows: REGISTRAR_BOOKINGS.map(b=>({ id:b.booking_id, registrar_name:b.registrar_name, date:b.date, time:`${b.start_time}–${b.end_time}`, status:b.status })),
-        onDelete: async row => {
-            const res = await api('DELETE',`/api/registrar-bookings/${row.id}`);
-            if(res && res.ok){ showToast('Booking removed.',''); await reload(); }
-            else showToast('Error removing booking.','danger');
-        }
-    });
-}
-
-function openRegistrarBookingModal(){
-    const w = CURRENT_WEDDING;
-    const opts = REGISTRARS.map(r=>`<option value="${r.registrar_id}">${r.name}</option>`).join('');
-    showModal('Book Registrar',
-        `<div class="form-grid">
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Registrar</label><select id="regSel">${opts}</select></div>
-            <div class="form-group"><label class="form-label">Date</label><input id="regDate" type="date" value="${w.date}"/></div>
-            <div class="form-group"><label class="form-label">Start Time</label><input id="regStart" type="time" value="10:00"/></div>
-            <div class="form-group"><label class="form-label">End Time</label><input id="regEnd" type="time" value="12:00"/></div>
-        </div>`,
-        async () => {
-            const res = await api('POST',`/api/weddings/${w.wedding_id}/registrar-bookings`,{
-                registrar_id: document.getElementById('regSel').value,
-                date: document.getElementById('regDate').value,
-                start_time: document.getElementById('regStart').value,
-                end_time: document.getElementById('regEnd').value,
-                status: 'confirmed'
-            });
-            if(res && res.ok){ closeModal(); showToast('Registrar booked!','success'); await reload(); }
-            else { const d=await res.json(); showToast(d.error||'Error booking registrar.','danger'); }
-        }
-    );
-}
-
-function openChurchBookingModal(){
-    const w = CURRENT_WEDDING;
-    const churchOptions = CHURCHES.map(c=>`<option value="${c.church_id}">${c.name}</option>`).join('');
-    showModal('Book a Church',
-        `<div class="form-grid">
-            <div class="form-group" style="grid-column:1/-1;"><label class="form-label">Church</label><select id="churchSelect">${churchOptions}</select></div>
-            <div class="form-group"><label class="form-label">Date</label><input id="churchDate" type="date" value="${w.date}"/></div>
-            <div class="form-group"><label class="form-label">Start Time</label><input id="churchStart" type="time" value="14:00"/></div>
-            <div class="form-group"><label class="form-label">End Time</label><input id="churchEnd" type="time" value="15:00"/></div>
-        </div>`,
-        async () => {
-            const res = await api('POST',`/api/weddings/${w.wedding_id}/church-bookings`,{
-                church_id: document.getElementById('churchSelect').value,
-                date: document.getElementById('churchDate').value,
-                start_time: document.getElementById('churchStart').value,
-                end_time: document.getElementById('churchEnd').value,
-                status: 'confirmed'
-            });
-            if(res && res.ok){ closeModal(); showToast('Church booked!','success'); await reload(); }
-            else { const d=await res.json(); showToast(d.error||'Error booking church.','danger'); }
-        }
-    );
-}
-
-async function deleteChurchBooking(id){
-    if(!confirm('Delete this church booking?')) return;
-    const res = await api('DELETE',`/api/church-bookings/${id}`);
-    if(res && res.ok){ showToast('Booking removed.','success'); await reload(); }
-    else showToast('Error removing booking.','danger');
-}
-
-// =============================================================
-//  SETTINGS PAGE
-// =============================================================
-function renderSettingsPage(container){
-    const u = CURRENT_USER;
-    const couple = getCoupleNames();
-    const wrap = document.createElement('div');
-    wrap.innerHTML = `
-    <div class="card">
-        <div class="card-header"><h3>Profile Settings</h3><span class="badge badge-rose">User #${u.user_id}</span></div>
-        <div class="gold-rule"></div>
-        <div style="display:flex;align-items:center;gap:16px;margin-bottom:24px;padding:16px;background:var(--cream);border-radius:var(--radius-lg);border:1px solid var(--border-light);">
-            <div style="width:56px;height:56px;border-radius:50%;background:linear-gradient(135deg,var(--blush-mid),var(--rose));display:flex;align-items:center;justify-content:center;color:white;font-weight:700;font-size:20px;flex-shrink:0;">
-                ${u.first_name[0]}${u.last_name[0]}
-            </div>
-            <div>
-                <div style="font-family:'Cormorant Garamond',serif;font-size:20px;font-weight:700;">${u.first_name} ${u.last_name}</div>
-                <div class="muted">${u.email}</div>
-            </div>
-        </div>
-        <div class="form-grid">
-            <div class="form-group"><label class="form-label">First Name</label><input id="sFirst" value="${u.first_name}"/></div>
-            <div class="form-group"><label class="form-label">Last Name</label><input id="sLast" value="${u.last_name}"/></div>
-            <div class="form-group"><label class="form-label">Email</label><input id="sEmail" type="email" value="${u.email}"/></div>
-            <div class="form-group"><label class="form-label">Phone</label><input id="sPhone" value="${u.phone_number||''}"/></div>
-            <div class="form-group"><label class="form-label">Gender</label>
-                <select id="sGender">
-                    <option ${u.gender==='Female'?'selected':''}>Female</option>
-                    <option ${u.gender==='Male'?'selected':''}>Male</option>
-                    <option ${u.gender==='Other'?'selected':''}>Other</option>
-                </select>
-            </div>
-        </div>
-        <div style="display:flex;gap:10px;margin-top:20px;">
-            <button class="btn btn-primary" id="btnSaveProfile">Save Changes</button>
-            <button class="btn btn-ghost" onclick="renderApp()">Reset</button>
-        </div>
-    </div>
-    <div class="card" style="margin-top:18px;">
-        <div class="card-header"><h3>💍 Invitation Names</h3></div>
-        <div class="gold-rule"></div>
-        <p class="muted" style="margin-bottom:16px;">These names are used on guest invitations </p>
-        <div class="form-grid">
-            <div class="form-group"><label class="form-label">Bride's Name</label><input id="sBrideName" placeholder="e.g. Maria" value="${escapeHtml(couple.bride_name)}"/></div>
-            <div class="form-group"><label class="form-label">Groom's Name</label><input id="sGroomName" placeholder="e.g. John" value="${escapeHtml(couple.groom_name)}"/></div>
-        </div>
-        <div style="display:flex;gap:10px;margin-top:20px;">
-            <button class="btn btn-primary" id="btnSaveCoupleNames">Save Names</button>
-        </div>
-    </div>`;
-    container.appendChild(wrap);
-
-    wrap.querySelector('#btnSaveCoupleNames').addEventListener('click', () => {
-        const bride_name = document.getElementById('sBrideName').value.trim();
-        const groom_name = document.getElementById('sGroomName').value.trim();
-        saveCoupleNames(bride_name, groom_name);
-        showToast('Invitation names saved on this device!','success');
-    });
-
-    wrap.querySelector('#btnSaveProfile').addEventListener('click', async () => {
-        const res = await api('PUT',`/api/users/${u.user_id}`,{
-            first_name: document.getElementById('sFirst').value,
-            last_name: document.getElementById('sLast').value,
-            email: document.getElementById('sEmail').value,
-            phone_number: document.getElementById('sPhone').value,
-            gender: document.getElementById('sGender').value
-        });
-        if(res && res.ok){
-            CURRENT_USER = await res.json();
-            document.getElementById('userName').textContent = `${CURRENT_USER.first_name} ${CURRENT_USER.last_name}`;
-            document.getElementById('userAvatar').textContent = `${CURRENT_USER.first_name[0]}${CURRENT_USER.last_name[0]}`;
-            showToast('Profile saved!','success');
-        } else showToast('Error saving profile.','danger');
-    });
-}
-
-// =============================================================
-//  UTILITY
-// =============================================================
-// When the window gains focus, refresh wedding-specific data so changes submitted via RSVP links
-// (which happen outside this UI) are reflected in both Guests and Seating pages.
-window.addEventListener('focus', () => { if (CURRENT_WEDDING) reload(); });
-
-function focusSearch(){
-    const s = document.querySelector('.search-input');
-    if(s) s.focus();
-}
-</script>
-</body>
-</html>
Index: d-church-booking.js
===================================================================
--- add-church-booking.js	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,51 +1,0 @@
-// Church booking table migration
-require('dotenv').config();
-const { Pool } = require('pg');
-
-const pool = new Pool({
-    user: process.env.DB_USER,
-    host: process.env.DB_HOST,
-    database: process.env.DB_NAME,
-    password: process.env.DB_PASSWORD,
-    port: parseInt(process.env.DB_PORT, 10),
-    ssl: { rejectUnauthorized: false }
-});
-
-async function addChurchBookingTable() {
-    try {
-        console.log('📋 Creating church_booking table...');
-        
-        const sql = `
-            CREATE TABLE IF NOT EXISTS project.church_booking (
-                booking_id SERIAL PRIMARY KEY,
-                "date"     DATE        NOT NULL,
-                start_time TIME        NOT NULL,
-                end_time   TIME        NOT NULL,
-                status     VARCHAR(30) NOT NULL,
-                church_id  INTEGER     NOT NULL,
-                wedding_id INTEGER     NOT NULL,
-                CONSTRAINT fk_cb_church  FOREIGN KEY (church_id)  REFERENCES project.church(church_id)   ON UPDATE CASCADE ON DELETE CASCADE,
-                CONSTRAINT fk_cb_wedding FOREIGN KEY (wedding_id) REFERENCES project.wedding(wedding_id) ON UPDATE CASCADE ON DELETE CASCADE,
-                CONSTRAINT chk_cb_time   CHECK (end_time > start_time)
-            );
-        `;
-        
-        await pool.query(sql);
-        console.log('✅ church_booking table created successfully!');
-        console.log('\n📌 Church booking endpoints are now available:');
-        console.log('   GET  /api/weddings/:wid/church-bookings');
-        console.log('   POST /api/weddings/:wid/church-bookings');
-        console.log('   DELETE /api/church-bookings/:id\n');
-        
-        await pool.end();
-        process.exit(0);
-    } catch (err) {
-        console.error('❌ Error creating table:');
-        console.error(err.message);
-        console.error('\nNote: Table might already exist. Check with your database client.\n');
-        await pool.end();
-        process.exit(1);
-    }
-}
-
-addChurchBookingTable();
Index: d-guest-role.js
===================================================================
--- add-guest-role.js	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,31 +1,0 @@
-// Add role column to guest table
-require('dotenv').config();
-const { Pool } = require('pg');
-
-const pool = new Pool({
-    user: process.env.DB_USER,
-    host: process.env.DB_HOST,
-    database: process.env.DB_NAME,
-    password: process.env.DB_PASSWORD,
-    port: parseInt(process.env.DB_PORT, 10),
-    ssl: { rejectUnauthorized: false }
-});
-
-async function addRoleColumn() {
-    try {
-        console.log('🔄 Adding role column to guest table...');
-        
-        await pool.query(`
-            ALTER TABLE project.guest 
-            ADD COLUMN IF NOT EXISTS role VARCHAR(100) DEFAULT 'Guest'
-        `);
-        
-        console.log('✅ Role column added successfully!');
-        process.exit(0);
-    } catch (err) {
-        console.error('❌ Error:', err.message);
-        process.exit(1);
-    }
-}
-
-addRoleColumn();
Index: vanced_constraints.sql
===================================================================
--- advanced_constraints.sql	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,15 +1,0 @@
-ALTER TABLE event_rsvp
-ADD CONSTRAINT chk_rsvp_status
-CHECK (status IN ('accepted', 'declined', 'pending'));
-
-ALTER TABLE attendance
-ADD CONSTRAINT chk_attendance_status
-CHECK (status IN ('attending', 'absent'));
-
-ALTER TABLE event_rsvp
-ADD CONSTRAINT uq_guest_event_rsvp
-UNIQUE (guest_id, event_id);
-
-ALTER TABLE attendance
-ADD CONSTRAINT uq_guest_event_attendance
-UNIQUE (guest_id, event_id);
Index: vanced_functions.sql
===================================================================
--- advanced_functions.sql	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,190 +1,0 @@
-CREATE OR REPLACE FUNCTION calculate_wedding_total_cost(p_wedding_id INTEGER)
-RETURNS NUMERIC AS $$
-DECLARE
-    venue_cost NUMERIC := 0;
-    photographer_cost NUMERIC := 0;
-    band_cost NUMERIC := 0;
-BEGIN
-    SELECT COALESCE(SUM(price), 0)
-    INTO venue_cost
-    FROM venue_booking
-    WHERE wedding_id = p_wedding_id
-      AND status = 'confirmed';
-
-    SELECT COALESCE(SUM(
-        EXTRACT(EPOCH FROM (pb.end_time - pb.start_time)) / 3600
-        * p.price_per_hour
-    ), 0)
-    INTO photographer_cost
-    FROM photographer_booking pb
-    JOIN photographer p ON pb.photographer_id = p.photographer_id
-    WHERE pb.wedding_id = p_wedding_id
-      AND pb.status = 'confirmed';
-
-    SELECT COALESCE(SUM(
-        EXTRACT(EPOCH FROM (bb.end_time - bb.start_time)) / 3600
-        * b.price_per_hour
-    ), 0)
-    INTO band_cost
-    FROM band_booking bb
-    JOIN band b ON bb.band_id = b.band_id
-    WHERE bb.wedding_id = p_wedding_id
-      AND bb.status = 'confirmed';
-
-    RETURN venue_cost + photographer_cost + band_cost;
-END;
-$$ LANGUAGE plpgsql;
-
-
-CREATE OR REPLACE FUNCTION is_venue_available(
-    p_venue_id INTEGER,
-    p_date DATE,
-    p_start TIME,
-    p_end TIME
-)
-RETURNS BOOLEAN AS $$
-BEGIN
-    IF p_end <= p_start THEN
-        RAISE EXCEPTION 'Invalid interval: end time must be after start time.';
-    END IF;
-
-    RETURN NOT EXISTS (
-        SELECT 1
-        FROM venue_booking vb
-        WHERE vb.venue_id = p_venue_id
-          AND vb.status <> 'cancelled'
-          AND (
-                ((p_date + p_start) >= (vb."date" + vb.start_time)
-                 AND (p_date + p_start) < (vb."date" + vb.end_time))
-
-                OR
-
-                ((p_date + p_end) > (vb."date" + vb.start_time)
-                 AND (p_date + p_end) <= (vb."date" + vb.end_time))
-
-                OR
-
-                ((p_date + p_start) <= (vb."date" + vb.start_time)
-                 AND (p_date + p_end) >= (vb."date" + vb.end_time))
-
-                OR
-
-                ((p_date + p_start) >= (vb."date" + vb.start_time)
-                 AND (p_date + p_end) <= (vb."date" + vb.end_time))
-          )
-    );
-END;
-$$ LANGUAGE plpgsql;
-
-
-CREATE OR REPLACE FUNCTION is_photographer_available(
-    p_photographer_id INTEGER,
-    p_date DATE,
-    p_start TIME,
-    p_end TIME
-)
-RETURNS BOOLEAN AS $$
-BEGIN
-    IF p_end <= p_start THEN
-        RAISE EXCEPTION 'Invalid interval: end time must be after start time.';
-    END IF;
-
-    RETURN NOT EXISTS (
-        SELECT 1
-        FROM photographer_booking pb
-        WHERE pb.photographer_id = p_photographer_id
-          AND pb.status <> 'cancelled'
-          AND (
-                ((p_date + p_start) >= (pb."date" + pb.start_time)
-                 AND (p_date + p_start) < (pb."date" + pb.end_time))
-
-                OR
-
-                ((p_date + p_end) > (pb."date" + pb.start_time)
-                 AND (p_date + p_end) <= (pb."date" + pb.end_time))
-
-                OR
-
-                ((p_date + p_start) <= (pb."date" + pb.start_time)
-                 AND (p_date + p_end) >= (pb."date" + pb.end_time))
-
-                OR
-
-                ((p_date + p_start) >= (pb."date" + pb.start_time)
-                 AND (p_date + p_end) <= (pb."date" + pb.end_time))
-          )
-    );
-END;
-$$ LANGUAGE plpgsql;
-
-
-CREATE OR REPLACE FUNCTION is_band_available(
-    p_band_id INTEGER,
-    p_date DATE,
-    p_start TIME,
-    p_end TIME
-)
-RETURNS BOOLEAN AS $$
-BEGIN
-    IF p_end <= p_start THEN
-        RAISE EXCEPTION 'Invalid interval: end time must be after start time.';
-    END IF;
-
-    RETURN NOT EXISTS (
-        SELECT 1
-        FROM band_booking bb
-        WHERE bb.band_id = p_band_id
-          AND bb.status <> 'cancelled'
-          AND (
-                ((p_date + p_start) >= (bb."date" + bb.start_time)
-                 AND (p_date + p_start) < (bb."date" + bb.end_time))
-
-                OR
-
-                ((p_date + p_end) > (bb."date" + bb.start_time)
-                 AND (p_date + p_end) <= (bb."date" + bb.end_time))
-
-                OR
-
-                ((p_date + p_start) <= (bb."date" + bb.start_time)
-                 AND (p_date + p_end) >= (bb."date" + bb.end_time))
-
-                OR
-
-                ((p_date + p_start) >= (bb."date" + bb.start_time)
-                 AND (p_date + p_end) <= (bb."date" + bb.end_time))
-          )
-    );
-END;
-$$ LANGUAGE plpgsql;
-
-
-CREATE OR REPLACE PROCEDURE generate_rsvp_summary(p_event_id INTEGER)
-LANGUAGE plpgsql
-AS $$
-BEGIN
-    RAISE NOTICE 'Accepted: %',
-    (
-        SELECT COUNT(*)
-        FROM event_rsvp
-        WHERE event_id = p_event_id
-          AND status = 'accepted'
-    );
-
-    RAISE NOTICE 'Declined: %',
-    (
-        SELECT COUNT(*)
-        FROM event_rsvp
-        WHERE event_id = p_event_id
-          AND status = 'declined'
-    );
-
-    RAISE NOTICE 'Pending: %',
-    (
-        SELECT COUNT(*)
-        FROM event_rsvp
-        WHERE event_id = p_event_id
-          AND status = 'pending'
-    );
-END;
-$$;
Index: vanced_testing.sql
===================================================================
--- advanced_testing.sql	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,154 +1,0 @@
--- 1. Existing venue booking used for overlap testing
-INSERT INTO venue_booking(
-    "date",
-    start_time,
-    end_time,
-    status,
-    price,
-    venue_id,
-    wedding_id
-)
-VALUES (
-    '2026-06-20',
-    '18:00',
-    '22:00',
-    'confirmed',
-    5000,
-    1,
-    1
-);
-
--- Overlap case 1: starts inside existing interval
-SELECT is_venue_available(1, '2026-06-20', '19:00', '23:00');
--- Expected result: FALSE
-
--- Overlap case 2: ends inside existing interval
-SELECT is_venue_available(1, '2026-06-20', '16:00', '19:00');
--- Expected result: FALSE
-
--- Overlap case 3: fully contains existing interval
-SELECT is_venue_available(1, '2026-06-20', '17:00', '23:00');
--- Expected result: FALSE
-
--- Overlap case 4: fully inside existing interval
-SELECT is_venue_available(1, '2026-06-20', '19:00', '21:00');
--- Expected result: FALSE
-
--- Non-overlap case 1: ends before existing interval
-SELECT is_venue_available(1, '2026-06-20', '14:00', '17:00');
--- Expected result: TRUE
-
--- Non-overlap case 2: starts after existing interval
-SELECT is_venue_available(1, '2026-06-20', '22:00', '23:00');
--- Expected result: TRUE
-
-
--- 2. Existing photographer booking used for overlap testing
-INSERT INTO photographer_booking(
-    "date",
-    start_time,
-    end_time,
-    status,
-    photographer_id,
-    wedding_id
-)
-VALUES (
-    '2026-06-20',
-    '18:00',
-    '22:00',
-    'confirmed',
-    1,
-    1
-);
-
--- Photographer exact overlap test
-SELECT is_photographer_available(1, '2026-06-20', '18:00', '22:00');
--- Expected result: FALSE
-
--- Photographer fully inside overlap test
-SELECT is_photographer_available(1, '2026-06-20', '19:00', '21:00');
--- Expected result: FALSE
-
-
--- 3. Existing band booking used for overlap testing
-INSERT INTO band_booking(
-    "date",
-    start_time,
-    end_time,
-    status,
-    band_id,
-    wedding_id
-)
-VALUES (
-    '2026-06-20',
-    '18:00',
-    '22:00',
-    'confirmed',
-    1,
-    1
-);
-
--- Band exact overlap test
-SELECT is_band_available(1, '2026-06-20', '18:00', '22:00');
--- Expected result: FALSE
-
--- Band fully contains overlap test
-SELECT is_band_available(1, '2026-06-20', '17:00', '23:00');
--- Expected result: FALSE
-
-
--- 4. RSVP / attendance validation test
-INSERT INTO attendance(
-    status,
-    table_number,
-    role,
-    guest_id,
-    event_id
-)
-VALUES (
-    'attending',
-    3,
-    'Guest',
-    1,
-    1
-);
-
--- Expected result:
--- Declined guests cannot be marked as attending.
-
-
--- 5. View tests
-SELECT *
-FROM vw_wedding_financial_summary;
-
-SELECT *
-FROM vw_rsvp_overview;
-
-SELECT *
-FROM vw_vendor_booking_overview;
-
-SELECT *
-FROM vw_upcoming_weddings;
-
-
--- 6. Constraint validation test
-INSERT INTO event_rsvp(
-    guest_id,
-    event_id,
-    status
-)
-VALUES (
-    1,
-    1,
-    'invalid_status'
-);
-
--- Expected result:
--- CHECK constraint violation
-
-
--- 7. Procedure test
-CALL generate_rsvp_summary(1);
-
--- Expected result:
--- NOTICE messages for accepted, declined, and pending RSVP counts
Index: vanced_triggers.sql
===================================================================
--- advanced_triggers.sql	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,182 +1,0 @@
-CREATE OR REPLACE FUNCTION check_venue_booking_overlap()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF NEW.end_time <= NEW.start_time THEN
-        RAISE EXCEPTION 'Invalid venue booking interval: end_time must be after start_time.';
-    END IF;
-
-    IF EXISTS (
-        SELECT 1
-        FROM venue_booking vb
-        WHERE vb.venue_id = NEW.venue_id
-          AND vb.status <> 'cancelled'
-          AND vb.booking_id <> COALESCE(NEW.booking_id, -1)
-          AND (
-                -- Case 1: new booking starts inside existing booking
-                ((NEW."date" + NEW.start_time) >= (vb."date" + vb.start_time)
-                 AND (NEW."date" + NEW.start_time) < (vb."date" + vb.end_time))
-
-                OR
-
-                -- Case 2: new booking ends inside existing booking
-                ((NEW."date" + NEW.end_time) > (vb."date" + vb.start_time)
-                 AND (NEW."date" + NEW.end_time) <= (vb."date" + vb.end_time))
-
-                OR
-
-                -- Case 3: new booking fully contains existing booking
-                ((NEW."date" + NEW.start_time) <= (vb."date" + vb.start_time)
-                 AND (NEW."date" + NEW.end_time) >= (vb."date" + vb.end_time))
-
-                OR
-
-                -- Case 4: new booking is fully inside existing booking
-                ((NEW."date" + NEW.start_time) >= (vb."date" + vb.start_time)
-                 AND (NEW."date" + NEW.end_time) <= (vb."date" + vb.end_time))
-          )
-    ) THEN
-        RAISE EXCEPTION 'Venue is already booked for this date and time interval.';
-    END IF;
-
-    RETURN NEW;
-END;
-$$ LANGUAGE plpgsql;
-
-DROP TRIGGER IF EXISTS trg_venue_booking_overlap ON venue_booking;
-
-CREATE TRIGGER trg_venue_booking_overlap
-BEFORE INSERT OR UPDATE ON venue_booking
-FOR EACH ROW
-EXECUTE FUNCTION check_venue_booking_overlap();
-
-
-CREATE OR REPLACE FUNCTION check_photographer_booking_overlap()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF NEW.end_time <= NEW.start_time THEN
-        RAISE EXCEPTION 'Invalid photographer booking interval: end_time must be after start_time.';
-    END IF;
-
-    IF EXISTS (
-        SELECT 1
-        FROM photographer_booking pb
-        WHERE pb.photographer_id = NEW.photographer_id
-          AND pb.status <> 'cancelled'
-          AND pb.booking_id <> COALESCE(NEW.booking_id, -1)
-          AND (
-                -- Case 1: new booking starts inside existing booking
-                ((NEW."date" + NEW.start_time) >= (pb."date" + pb.start_time)
-                 AND (NEW."date" + NEW.start_time) < (pb."date" + pb.end_time))
-
-                OR
-
-                -- Case 2: new booking ends inside existing booking
-                ((NEW."date" + NEW.end_time) > (pb."date" + pb.start_time)
-                 AND (NEW."date" + NEW.end_time) <= (pb."date" + pb.end_time))
-
-                OR
-
-                -- Case 3: new booking fully contains existing booking
-                ((NEW."date" + NEW.start_time) <= (pb."date" + pb.start_time)
-                 AND (NEW."date" + NEW.end_time) >= (pb."date" + pb.end_time))
-
-                OR
-
-                -- Case 4: new booking is fully inside existing booking
-                ((NEW."date" + NEW.start_time) >= (pb."date" + pb.start_time)
-                 AND (NEW."date" + NEW.end_time) <= (pb."date" + pb.end_time))
-          )
-    ) THEN
-        RAISE EXCEPTION 'Photographer is already booked for this date and time interval.';
-    END IF;
-
-    RETURN NEW;
-END;
-$$ LANGUAGE plpgsql;
-
-DROP TRIGGER IF EXISTS trg_photographer_booking_overlap ON photographer_booking;
-
-CREATE TRIGGER trg_photographer_booking_overlap
-BEFORE INSERT OR UPDATE ON photographer_booking
-FOR EACH ROW
-EXECUTE FUNCTION check_photographer_booking_overlap();
-
-
-CREATE OR REPLACE FUNCTION check_band_booking_overlap()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF NEW.end_time <= NEW.start_time THEN
-        RAISE EXCEPTION 'Invalid band booking interval: end_time must be after start_time.';
-    END IF;
-
-    IF EXISTS (
-        SELECT 1
-        FROM band_booking bb
-        WHERE bb.band_id = NEW.band_id
-          AND bb.status <> 'cancelled'
-          AND bb.booking_id <> COALESCE(NEW.booking_id, -1)
-          AND (
-                -- Case 1: new booking starts inside existing booking
-                ((NEW."date" + NEW.start_time) >= (bb."date" + bb.start_time)
-                 AND (NEW."date" + NEW.start_time) < (bb."date" + bb.end_time))
-
-                OR
-
-                -- Case 2: new booking ends inside existing booking
-                ((NEW."date" + NEW.end_time) > (bb."date" + bb.start_time)
-                 AND (NEW."date" + NEW.end_time) <= (bb."date" + bb.end_time))
-
-                OR
-
-                -- Case 3: new booking fully contains existing booking
-                ((NEW."date" + NEW.start_time) <= (bb."date" + bb.start_time)
-                 AND (NEW."date" + NEW.end_time) >= (bb."date" + bb.end_time))
-
-                OR
-
-                -- Case 4: new booking is fully inside existing booking
-                ((NEW."date" + NEW.start_time) >= (bb."date" + bb.start_time)
-                 AND (NEW."date" + NEW.end_time) <= (bb."date" + bb.end_time))
-          )
-    ) THEN
-        RAISE EXCEPTION 'Band is already booked for this date and time interval.';
-    END IF;
-
-    RETURN NEW;
-END;
-$$ LANGUAGE plpgsql;
-
-DROP TRIGGER IF EXISTS trg_band_booking_overlap ON band_booking;
-
-CREATE TRIGGER trg_band_booking_overlap
-BEFORE INSERT OR UPDATE ON band_booking
-FOR EACH ROW
-EXECUTE FUNCTION check_band_booking_overlap();
-
-
-CREATE OR REPLACE FUNCTION validate_attendance_consistency()
-RETURNS TRIGGER AS $$
-DECLARE
-    guest_rsvp VARCHAR(30);
-BEGIN
-    SELECT status
-    INTO guest_rsvp
-    FROM event_rsvp
-    WHERE guest_id = NEW.guest_id
-      AND event_id = NEW.event_id;
-
-    IF guest_rsvp = 'declined'
-       AND NEW.status = 'attending' THEN
-        RAISE EXCEPTION 'Declined guests cannot be marked as attending.';
-    END IF;
-
-    RETURN NEW;
-END;
-$$ LANGUAGE plpgsql;
-
-DROP TRIGGER IF EXISTS trg_attendance_consistency ON attendance;
-
-CREATE TRIGGER trg_attendance_consistency
-BEFORE INSERT OR UPDATE ON attendance
-FOR EACH ROW
-EXECUTE FUNCTION validate_attendance_consistency();
Index: vanced_views.sql
===================================================================
--- advanced_views.sql	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,43 +1,0 @@
-CREATE OR REPLACE VIEW vw_wedding_financial_summary AS
-SELECT
-    w.wedding_id,
-    w.budget,
-    calculate_wedding_total_cost(w.wedding_id) AS total_cost,
-    w.budget - calculate_wedding_total_cost(w.wedding_id) AS remaining_budget
-FROM wedding w;
-
-
-CREATE OR REPLACE VIEW vw_rsvp_overview AS
-SELECT
-    e.event_id,
-    e.event_type,
-    r.status,
-    COUNT(*) AS total
-FROM event_rsvp r
-JOIN event e ON r.event_id = e.event_id
-GROUP BY e.event_id, e.event_type, r.status;
-
-
-CREATE OR REPLACE VIEW vw_vendor_booking_overview AS
-SELECT DISTINCT
-    w.wedding_id,
-    v.name AS venue_name,
-    p.name AS photographer_name,
-    b.band_name
-FROM wedding w
-LEFT JOIN venue_booking vb ON w.wedding_id = vb.wedding_id
-LEFT JOIN venue v ON vb.venue_id = v.venue_id
-LEFT JOIN photographer_booking pb ON w.wedding_id = pb.wedding_id
-LEFT JOIN photographer p ON pb.photographer_id = p.photographer_id
-LEFT JOIN band_booking bb ON w.wedding_id = bb.wedding_id
-LEFT JOIN band b ON bb.band_id = b.band_id;
-
-
-CREATE OR REPLACE VIEW vw_upcoming_weddings AS
-SELECT
-    wedding_id,
-    "date" AS wedding_date,
-    budget,
-    notes
-FROM wedding
-WHERE "date" >= CURRENT_DATE;
Index: ta_load (1).sql
===================================================================
--- data_load (1).sql	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,97 +1,0 @@
-SET search_path TO project;
-
-TRUNCATE TABLE
-    event_rsvp,
-    attendance,
-    event,
-    guest,
-    priest,
-    church,
-    registrar_booking,
-    registrar,
-    venue_booking,
-    photographer_booking,
-    band_booking,
-    venue,
-    venue_type,
-    photographer,
-    band,
-    wedding,
-    "user"
-RESTART IDENTITY CASCADE;
-
--- Demo user for sample bookings (use this to test before creating your own wedding)
-INSERT INTO "user"(first_name, last_name, email, password_hash, phone_number, gender, birthday) VALUES
-('Demo', 'User', 'demo@wedding.app', '$2a$10$YourHashedDemoPasswordHere123456789', '+38970000000', 'Other', NULL);
-
--- Demo weddings to showcase booking features
-INSERT INTO wedding("date", budget, notes, user_id) VALUES
-('2025-06-15', 5000, 'Sample wedding - replace with your own', 1),
-('2025-07-20', 6000, 'Sample wedding - replace with your own', 1);
-
-INSERT INTO church(name, location, contact) VALUES
-('St. Clement Church', 'Skopje', 'contact@church.mk'),
-('St. Panteleimon', 'Nerezi', 'info@church.mk');
-
-INSERT INTO priest(name, contact, church_id) VALUES
-('Father Nikola', '+38970123456', 1),
-('Father Petar', '+38970222333', 2);
-
-INSERT INTO venue_type(type_name) VALUES
-('Restaurant'), ('Wedding Hall'), ('Outdoor Garden');
-
-INSERT INTO venue(name, location, city, address, capacity, menu, phone_number, price_per_guest, type_id) VALUES
-('Lakeside Garden', 'Matka', 'Skopje', 'Matka 12', 200, 'Garden menu', '+38971123456', 35, 3),
-('Royal Hall', 'Centar', 'Skopje', 'Main St 5', 350, 'Full menu', '+38972234567', 45, 2);
-
-INSERT INTO photographer(name, email, phone_number, price_per_hour) VALUES
-('Luna Studio', 'luna@studio.mk', '+38970101010', 55),
-('Golden Frame', 'golden@frame.mk', '+38970202020', 65);
-
-INSERT INTO band(band_name, genre, equipment, phone_number, price_per_hour) VALUES
-('Wedding Vibes', 'Pop', 'Sound + lights', '+38970909090', 80),
-('Balkan Groove', 'Traditional', 'Full instruments', '+38970707070', 95);
-
-INSERT INTO registrar(name, contact, location, working_hours) VALUES
-('Skopje Civil Registry', '+38970123456', 'Skopje', '08:00-16:00'),
-('Centar Registry', '+38970222333', 'Skopje', '09:00-17:00');
-
-INSERT INTO venue_booking("date", start_time, end_time, status, price, venue_id, wedding_id) VALUES
-('2025-06-15', '18:00', '23:00', 'confirmed', 3500, 1, 1),
-('2025-07-20', '17:00', '22:00', 'confirmed', 4500, 2, 2);
-
-INSERT INTO photographer_booking("date", start_time, end_time, status, photographer_id, wedding_id) VALUES
-('2025-06-15', '16:00', '23:00', 'confirmed', 1, 1),
-('2025-07-20', '15:00', '22:00', 'confirmed', 2, 2);
-
-INSERT INTO band_booking("date", start_time, end_time, status, band_id, wedding_id) VALUES
-('2025-06-15', '18:30', '23:00', 'confirmed', 1, 1),
-('2025-07-20', '19:00', '23:00', 'confirmed', 2, 2);
-
-INSERT INTO registrar_booking("date", start_time, end_time, status, registrar_id, wedding_id) VALUES
-('2025-06-15', '10:00', '12:00', 'confirmed', 1, 1),
-('2025-07-20', '10:00', '12:00', 'confirmed', 2, 2);
-
-INSERT INTO event(event_type, "date", start_time, end_time, status, wedding_id) VALUES
-('Ceremony', '2025-06-15', '16:00', '17:30', 'scheduled', 1),
-('Reception', '2025-06-15', '18:00', '23:00', 'scheduled', 1),
-('Ceremony', '2025-07-20', '15:00', '16:30', 'scheduled', 2),
-('Reception', '2025-07-20', '17:00', '22:00', 'scheduled', 2);
-
-INSERT INTO guest(first_name, last_name, email, role, wedding_id) VALUES
-('John', 'Smith', 'john@email.com', 'Best Man', 1),
-('Mary', 'Johnson', 'mary@email.com', 'Bridesmaid', 1),
-('Alex', 'Williams', 'alex@email.com', 'Groomsman', 2),
-('Sarah', 'Brown', 'sarah@email.com', 'Guest', 2);
-
-INSERT INTO event_rsvp(status, response_date, guest_id, event_id) VALUES
-('accepted', '2025-05-20', 1, 1),
-('accepted', '2025-05-21', 2, 2),
-('declined', '2025-05-22', 3, 3),
-('pending', '2025-05-23', 4, 4);
-
-INSERT INTO attendance(status, table_number, role, guest_id, event_id) VALUES
-('attending', 5, 'Guest', 1, 2),
-('attending', 7, 'Guest', 2, 2),
-('attending', 8, 'Guest', 3, 4),
-('not_attending', NULL, 'Guest', 4, 4);
Index: x-passwords.js
===================================================================
--- fix-passwords.js	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,51 +1,0 @@
-// Fix sample user passwords
-require('dotenv').config();
-const { Pool } = require('pg');
-const bcrypt = require('bcryptjs');
-
-const pool = new Pool({
-    user: process.env.DB_USER,
-    host: process.env.DB_HOST,
-    database: process.env.DB_NAME,
-    password: process.env.DB_PASSWORD,
-    port: parseInt(process.env.DB_PORT, 10),
-    ssl: { rejectUnauthorized: false }
-});
-
-async function fixPasswords() {
-    try {
-        console.log('🔐 Fixing sample user passwords...\n');
-        
-        // Sample users: password = "password123"
-        const testPassword = 'password123';
-        const hash = await bcrypt.hash(testPassword, 10);
-        
-        const result = await pool.query(
-            `UPDATE project."user" 
-             SET password_hash = $1 
-             WHERE email IN ('ana.trajkovska@gmail.com', 'stefan.petrovski@gmail.com')
-             RETURNING user_id, email, password_hash`
-        , [hash]);
-        
-        console.log('✅ Updated users:');
-        result.rows.forEach(row => {
-            console.log(`   📧 ${row.email}`);
-        });
-        
-        console.log('\n🔑 Test credentials:');
-        console.log('   Email:    ana.trajkovska@gmail.com');
-        console.log('   Password: password123');
-        console.log('   —— OR ——');
-        console.log('   Email:    stefan.petrovski@gmail.com');
-        console.log('   Password: password123\n');
-        
-        await pool.end();
-        process.exit(0);
-    } catch (err) {
-        console.error('❌ Error:', err.message);
-        await pool.end();
-        process.exit(1);
-    }
-}
-
-fixPasswords();
Index: it-schema.js
===================================================================
--- init-schema.js	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,60 +1,0 @@
-// Schema initializer script
-require('dotenv').config();
-const { Pool } = require('pg');
-const fs = require('fs');
-const path = require('path');
-
-const pool = new Pool({
-    user: process.env.DB_USER,
-    host: process.env.DB_HOST,
-    database: process.env.DB_NAME,
-    password: process.env.DB_PASSWORD,
-    port: parseInt(process.env.DB_PORT, 10),
-    ssl: { rejectUnauthorized: false }
-});
-
-async function initializeSchema() {
-    try {
-        console.log('📂 Reading schema file...');
-        const schemaFile = path.join(__dirname, 'schema_creation (1).sql');
-        const schemaSql = fs.readFileSync(schemaFile, 'utf8');
-        
-        console.log('🗄️  Executing schema creation...');
-        await pool.query(schemaSql);
-        
-        console.log('✅ Schema created successfully!');
-        console.log('\nTables created:');
-        console.log('  - project.user');
-        console.log('  - project.wedding');
-        console.log('  - project.event');
-        console.log('  - project.guest');
-        console.log('  - project.event_rsvp');
-        console.log('  - project.attendance');
-        console.log('  - project.venue');
-        console.log('  - project.venue_booking');
-        console.log('  - project.church');
-        console.log('  - project.priest');
-        console.log('  - project.photographer');
-        console.log('  - project.vendor');
-        console.log('  - project.budget_item');
-        console.log('  - project.event_decoration');
-        console.log('  - project.decoration');
-        console.log('  - project.seating_chart');
-        console.log('  - project.flower_decoration');
-        console.log('  - And more...\n');
-        
-        await pool.end();
-        process.exit(0);
-    } catch (err) {
-        console.error('❌ Schema creation error:');
-        console.error(err.message);
-        console.error('\nMake sure:');
-        console.error('  1. SSH tunnel is active (port 9999)');
-        console.error('  2. Database credentials in .env are correct');
-        console.error('  3. You have permission to create schema');
-        await pool.end();
-        process.exit(1);
-    }
-}
-
-initializeSchema();
Index: ad-data.js
===================================================================
--- load-data.js	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,51 +1,0 @@
-// Data loader script
-require('dotenv').config();
-const { Pool } = require('pg');
-const fs = require('fs');
-const path = require('path');
-
-const pool = new Pool({
-    user: process.env.DB_USER,
-    host: process.env.DB_HOST,
-    database: process.env.DB_NAME,
-    password: process.env.DB_PASSWORD,
-    port: parseInt(process.env.DB_PORT, 10),
-    ssl: { rejectUnauthorized: false }
-});
-
-async function loadData() {
-    try {
-        console.log('📂 Reading data file...');
-        const dataFile = path.join(__dirname, 'data_load (1).sql');
-        const dataSql = fs.readFileSync(dataFile, 'utf8');
-        
-        console.log('📥 Loading sample data...');
-        await pool.query(dataSql);
-        
-        console.log('✅ Sample data loaded successfully!\n');
-        console.log('🎊 Added:');
-        console.log('  - 2 Users');
-        console.log('  - 2 Weddings');
-        console.log('  - 2 Venues');
-        console.log('  - 2 Photographers');
-        console.log('  - 2 Bands');
-        console.log('  - 2 Registrars');
-        console.log('  - 2 Events');
-        console.log('  - 2 Guests');
-        console.log('  - Sample bookings for all services\n');
-        
-        await pool.end();
-        process.exit(0);
-    } catch (err) {
-        console.error('❌ Data loading error:');
-        console.error(err.message);
-        console.error('\nCheck:');
-        console.error('  1. SSH tunnel is active (port 9999)');
-        console.error('  2. Database credentials in .env');
-        console.error('  3. Schema was created (run init-schema.js first)\n');
-        await pool.end();
-        process.exit(1);
-    }
-}
-
-loadData();
Index: gin.html
===================================================================
--- login.html	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,413 +1,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <title>Wedding Planner — Login</title>
-    <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet"/>
-    <style>
-        :root {
-            --cream:#fdf8f3; --cream-2:#f9f1e8; --cream-3:#f2e8db;
-            --blush-light:#fce8e8; --blush:#f4b8b8; --blush-mid:#e8949c;
-            --rose:#d4606a; --rose-deep:#b84550;
-            --gold-light:#f7e6b0; --gold:#c9a84c; --gold-dark:#9e7a28;
-            --text-dark:#2c1f1f; --text-mid:#6b4f4f; --text-light:#a08080;
-            --border:#ecddd0; --border-light:#f4ebe2;
-            --green:#3a9e6b; --red:#c94545;
-            --shadow-md:0 6px 20px rgba(44,31,31,.10),0 2px 6px rgba(44,31,31,.06);
-            --shadow-lg:0 16px 40px rgba(44,31,31,.14),0 4px 12px rgba(44,31,31,.08);
-            --radius-md:10px; --radius-lg:16px; --radius-xl:22px;
-            --transition:200ms cubic-bezier(.4,0,.2,1);
-        }
-        *,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
-        html,body{height:100%;font-family:'DM Sans',sans-serif;background:var(--cream);color:var(--text-dark);-webkit-font-smoothing:antialiased;}
-
-        body {
-            display:flex; align-items:center; justify-content:center;
-            min-height:100vh;
-            background: linear-gradient(135deg, #fdf8f3 0%, #f9f1e8 50%, #fce8e8 100%);
-        }
-
-        /* floating petals background */
-        body::before {
-            content:'💍';
-            position:fixed; top:10%; left:8%; font-size:80px; opacity:.06;
-            transform:rotate(-15deg); pointer-events:none;
-        }
-        body::after {
-            content:'🌸';
-            position:fixed; bottom:10%; right:8%; font-size:100px; opacity:.06;
-            transform:rotate(20deg); pointer-events:none;
-        }
-
-        .auth-wrapper {
-            width:100%; max-width:460px; padding:20px;
-        }
-
-        /* logo top */
-        .auth-logo {
-            text-align:center; margin-bottom:28px;
-        }
-        .auth-logo .logo-mark {
-            width:52px; height:52px; margin:0 auto 12px;
-            background:linear-gradient(135deg,var(--rose),var(--blush-mid));
-            border-radius:14px;
-            display:flex; align-items:center; justify-content:center;
-            box-shadow:0 4px 16px rgba(212,96,106,.35);
-        }
-        .auth-logo .logo-mark svg{width:26px;height:26px;}
-        .auth-logo h1 {
-            font-family:'Cormorant Garamond',serif;
-            font-size:28px; font-weight:700; color:var(--text-dark);
-        }
-        .auth-logo p { font-size:13px; color:var(--text-light); margin-top:4px; }
-
-        /* card */
-        .auth-card {
-            background:#fff;
-            border-radius:var(--radius-xl);
-            border:1px solid var(--border-light);
-            box-shadow:var(--shadow-lg);
-            overflow:hidden;
-        }
-
-        /* tabs */
-        .auth-tabs {
-            display:grid; grid-template-columns:1fr 1fr;
-            border-bottom:1px solid var(--border-light);
-        }
-        .auth-tab {
-            padding:16px; text-align:center; cursor:pointer;
-            font-size:14px; font-weight:500; color:var(--text-light);
-            background:#fff; border:none; outline:none;
-            transition:color var(--transition), background var(--transition);
-        }
-        .auth-tab.active {
-            color:var(--rose); font-weight:600;
-            background:var(--blush-light);
-            border-bottom:2px solid var(--rose);
-        }
-        .auth-tab:hover:not(.active){ background:var(--cream); color:var(--text-mid); }
-
-        /* form panels */
-        .auth-panel { display:none; padding:32px; }
-        .auth-panel.active { display:block; }
-
-        .gold-rule {
-            width:36px; height:2px;
-            background:linear-gradient(90deg,var(--gold),transparent);
-            border-radius:2px; margin:0 0 22px;
-        }
-
-        .form-group { margin-bottom:16px; }
-        .form-label {
-            display:block; font-size:12px; font-weight:600;
-            color:var(--text-mid); text-transform:uppercase;
-            letter-spacing:.06em; margin-bottom:6px;
-        }
-        input, select {
-            width:100%; padding:10px 14px;
-            border-radius:var(--radius-md); border:1px solid var(--border);
-            background:var(--cream); font-family:'DM Sans',sans-serif;
-            font-size:14px; color:var(--text-dark); outline:none;
-            transition:border-color var(--transition), background var(--transition);
-        }
-        input:focus, select:focus { border-color:var(--blush-mid); background:#fff; }
-
-        .form-row { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
-
-        .btn-submit {
-            width:100%; padding:12px;
-            background:var(--rose); color:#fff; border:none;
-            border-radius:var(--radius-md); font-family:'DM Sans',sans-serif;
-            font-size:15px; font-weight:600; cursor:pointer; margin-top:8px;
-            box-shadow:0 3px 12px rgba(212,96,106,.35);
-            transition:background var(--transition), box-shadow var(--transition), transform var(--transition);
-        }
-        .btn-submit:hover { background:var(--rose-deep); box-shadow:0 5px 18px rgba(212,96,106,.45); }
-        .btn-submit:active { transform:translateY(1px); }
-        .btn-submit:disabled { background:var(--blush); cursor:not-allowed; box-shadow:none; }
-
-        /* alert */
-        .alert {
-            padding:10px 14px; border-radius:var(--radius-md);
-            font-size:13px; font-weight:500; margin-bottom:16px;
-            display:none;
-        }
-        .alert.show { display:block; }
-        .alert-error { background:var(--red-light,#fdeaea); color:var(--red); border:1px solid #f5c2c2; }
-        .alert-success { background:#e4f5ec; color:var(--green); border:1px solid #b2e0ca; }
-
-        /* divider */
-        .form-divider {
-            display:flex; align-items:center; gap:10px;
-            margin:14px 0; color:var(--text-light); font-size:12px;
-        }
-        .form-divider::before, .form-divider::after {
-            content:''; flex:1; height:1px; background:var(--border-light);
-        }
-
-        /* footer note */
-        .auth-footer {
-            text-align:center; padding:16px 32px;
-            background:var(--cream); border-top:1px solid var(--border-light);
-            font-size:12px; color:var(--text-light);
-        }
-
-        /* loading spinner inside button */
-        .spinner {
-            display:inline-block; width:14px; height:14px;
-            border:2px solid rgba(255,255,255,.4);
-            border-top-color:#fff; border-radius:50%;
-            animation:spin .6s linear infinite; margin-right:6px; vertical-align:middle;
-        }
-        @keyframes spin { to { transform:rotate(360deg); } }
-    </style>
-</head>
-<body>
-
-<div class="auth-wrapper">
-
-    <!-- Logo -->
-    <div class="auth-logo">
-        <div class="logo-mark">
-            <svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
-                <path d="M12 21.593c-5.63-5.539-11-10.297-11-14.402C1 3.402 4.248 1 7.5 1 9.849 1 11.271 2.007 12 3.011 12.729 2.007 14.151 1 16.5 1 19.752 1 23 3.402 23 7.191c0 4.105-5.37 8.863-11 14.402z"/>
-            </svg>
-        </div>
-        <h1>Wedding Planner</h1>
-        <p>Plan your perfect day</p>
-    </div>
-
-    <!-- Card -->
-    <div class="auth-card">
-
-        <!-- Tabs -->
-        <div class="auth-tabs">
-            <button class="auth-tab active" id="tabLogin" onclick="switchTab('login')">Sign In</button>
-            <button class="auth-tab" id="tabRegister" onclick="switchTab('register')">Create Account</button>
-        </div>
-
-        <!-- LOGIN PANEL -->
-        <div class="auth-panel active" id="panelLogin">
-            <div class="gold-rule"></div>
-
-            <div class="alert alert-error" id="loginError"></div>
-            <div class="alert alert-success" id="loginSuccess"></div>
-
-            <div class="form-group">
-                <label class="form-label">Email Address</label>
-                <input type="email" id="loginEmail" placeholder="your@email.com" autocomplete="email"/>
-            </div>
-            <div class="form-group">
-                <label class="form-label">Password</label>
-                <input type="password" id="loginPassword" placeholder="Your password" autocomplete="current-password"/>
-            </div>
-
-            <button class="btn-submit" id="btnLogin" onclick="doLogin()">Sign In</button>
-
-            <div class="form-divider">or</div>
-            <p style="text-align:center;font-size:13px;color:var(--text-light);">
-                Don't have an account?
-                <a href="#" onclick="switchTab('register')" style="color:var(--rose);font-weight:600;text-decoration:none;">Create one free</a>
-            </p>
-        </div>
-
-        <!-- REGISTER PANEL -->
-        <div class="auth-panel" id="panelRegister">
-            <div class="gold-rule"></div>
-
-            <div class="alert alert-error" id="registerError"></div>
-            <div class="alert alert-success" id="registerSuccess"></div>
-
-            <div class="form-row">
-                <div class="form-group">
-                    <label class="form-label">First Name</label>
-                    <input type="text" id="regFirst" placeholder="Ana"/>
-                </div>
-                <div class="form-group">
-                    <label class="form-label">Last Name</label>
-                    <input type="text" id="regLast" placeholder="Trajkovska"/>
-                </div>
-            </div>
-
-            <div class="form-group">
-                <label class="form-label">Email Address</label>
-                <input type="email" id="regEmail" placeholder="your@email.com"/>
-            </div>
-
-            <div class="form-group">
-                <label class="form-label">Password</label>
-                <input type="password" id="regPassword" placeholder="Min. 6 characters"/>
-            </div>
-
-            <div class="form-row">
-                <div class="form-group">
-                    <label class="form-label">Phone (optional)</label>
-                    <input type="text" id="regPhone" placeholder="+38970..."/>
-                </div>
-                <div class="form-group">
-                    <label class="form-label">Gender (optional)</label>
-                    <select id="regGender">
-                        <option value="">Select...</option>
-                        <option value="Female">Female</option>
-                        <option value="Male">Male</option>
-                        <option value="Other">Other</option>
-                    </select>
-                </div>
-            </div>
-
-            <div class="form-group">
-                <label class="form-label">Birthday (optional)</label>
-                <input type="date" id="regBirthday"/>
-            </div>
-
-            <button class="btn-submit" id="btnRegister" onclick="doRegister()">Create Account</button>
-
-            <div class="form-divider">or</div>
-            <p style="text-align:center;font-size:13px;color:var(--text-light);">
-                Already have an account?
-                <a href="#" onclick="switchTab('login')" style="color:var(--rose);font-weight:600;text-decoration:none;">Sign in</a>
-            </p>
-        </div>
-
-        <div class="auth-footer">
-            Wedding Planner · University Database Project 2025
-        </div>
-    </div>
-</div>
-
-<script>
-    const API = 'http://localhost:3000';
-
-    // ── Check if already logged in ──────────────────────────
-    window.addEventListener('DOMContentLoaded', async () => {
-        try {
-            const res = await fetch(`${API}/api/auth/me`, { credentials: 'include' });
-            if (res.ok) {
-                window.location.href = 'Wedding_Planner.html';
-            }
-        } catch (e) { /* not logged in, stay on page */ }
-    });
-
-    // ── Tab switching ────────────────────────────────────────
-    function switchTab(tab) {
-        document.getElementById('panelLogin').classList.toggle('active', tab === 'login');
-        document.getElementById('panelRegister').classList.toggle('active', tab === 'register');
-        document.getElementById('tabLogin').classList.toggle('active', tab === 'login');
-        document.getElementById('tabRegister').classList.toggle('active', tab === 'register');
-        clearAlerts();
-    }
-
-    function clearAlerts() {
-        ['loginError','loginSuccess','registerError','registerSuccess'].forEach(id => {
-            const el = document.getElementById(id);
-            el.classList.remove('show');
-            el.textContent = '';
-        });
-    }
-
-    function showAlert(id, msg) {
-        const el = document.getElementById(id);
-        el.textContent = msg;
-        el.classList.add('show');
-    }
-
-    function setLoading(btnId, loading) {
-        const btn = document.getElementById(btnId);
-        if (loading) {
-            btn.disabled = true;
-            btn.innerHTML = '<span class="spinner"></span>Please wait...';
-        } else {
-            btn.disabled = false;
-            btn.innerHTML = btnId === 'btnLogin' ? 'Sign In' : 'Create Account';
-        }
-    }
-
-    // ── Login ────────────────────────────────────────────────
-    async function doLogin() {
-        clearAlerts();
-        const email    = document.getElementById('loginEmail').value.trim();
-        const password = document.getElementById('loginPassword').value;
-
-        if (!email || !password) {
-            showAlert('loginError', 'Please enter your email and password.');
-            return;
-        }
-
-        setLoading('btnLogin', true);
-        try {
-            const res = await fetch(`${API}/api/auth/login`, {
-                method: 'POST',
-                headers: { 'Content-Type': 'application/json' },
-                credentials: 'include',
-                body: JSON.stringify({ email, password })
-            });
-            const data = await res.json();
-
-            if (!res.ok) {
-                showAlert('loginError', data.error || 'Login failed.');
-            } else {
-                showAlert('loginSuccess', `Welcome back, ${data.user.first_name}! Redirecting...`);
-                setTimeout(() => { window.location.href = 'Wedding_Planner.html'; }, 900);
-            }
-        } catch (err) {
-            showAlert('loginError', 'Cannot connect to server. Is it running?');
-        } finally {
-            setLoading('btnLogin', false);
-        }
-    }
-
-    // ── Register ─────────────────────────────────────────────
-    async function doRegister() {
-        clearAlerts();
-        const first_name   = document.getElementById('regFirst').value.trim();
-        const last_name    = document.getElementById('regLast').value.trim();
-        const email        = document.getElementById('regEmail').value.trim();
-        const password     = document.getElementById('regPassword').value;
-        const phone_number = document.getElementById('regPhone').value.trim();
-        const gender       = document.getElementById('regGender').value;
-        const birthday     = document.getElementById('regBirthday').value;
-
-        if (!first_name || !last_name || !email || !password) {
-            showAlert('registerError', 'First name, last name, email and password are required.');
-            return;
-        }
-        if (password.length < 6) {
-            showAlert('registerError', 'Password must be at least 6 characters.');
-            return;
-        }
-
-        setLoading('btnRegister', true);
-        try {
-            const res = await fetch(`${API}/api/auth/register`, {
-                method: 'POST',
-                headers: { 'Content-Type': 'application/json' },
-                credentials: 'include',
-                body: JSON.stringify({ first_name, last_name, email, password, phone_number, gender, birthday })
-            });
-            const data = await res.json();
-
-            if (!res.ok) {
-                showAlert('registerError', data.error || 'Registration failed.');
-            } else {
-                showAlert('registerSuccess', `Account created! Welcome, ${data.user.first_name}! Redirecting...`);
-                setTimeout(() => { window.location.href = 'Wedding_Planner.html'; }, 1200);
-            }
-        } catch (err) {
-            showAlert('registerError', 'Cannot connect to server. Is it running?');
-        } finally {
-            setLoading('btnRegister', false);
-        }
-    }
-
-    // ── Allow Enter key to submit ────────────────────────────
-    document.addEventListener('keydown', e => {
-        if (e.key === 'Enter') {
-            const loginActive = document.getElementById('panelLogin').classList.contains('active');
-            if (loginActive) doLogin(); else doRegister();
-        }
-    });
-</script>
-</body>
-</html>
Index: ckage-lock.json
===================================================================
--- package-lock.json	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,1704 +1,0 @@
-{
-  "name": "wedding-planner",
-  "version": "1.0.0",
-  "lockfileVersion": 3,
-  "requires": true,
-  "packages": {
-    "": {
-      "name": "wedding-planner",
-      "version": "1.0.0",
-      "dependencies": {
-        "bcrypt": "^5.1.1",
-        "bcryptjs": "^3.0.3",
-        "cors": "^2.8.5",
-        "dotenv": "^17.3.1",
-        "express": "^4.18.2",
-        "express-session": "^1.17.3",
-        "nodemailer": "^6.9.9",
-        "pg": "^8.11.3"
-      }
-    },
-    "node_modules/@mapbox/node-pre-gyp": {
-      "version": "1.0.11",
-      "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
-      "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "detect-libc": "^2.0.0",
-        "https-proxy-agent": "^5.0.0",
-        "make-dir": "^3.1.0",
-        "node-fetch": "^2.6.7",
-        "nopt": "^5.0.0",
-        "npmlog": "^5.0.1",
-        "rimraf": "^3.0.2",
-        "semver": "^7.3.5",
-        "tar": "^6.1.11"
-      },
-      "bin": {
-        "node-pre-gyp": "bin/node-pre-gyp"
-      }
-    },
-    "node_modules/abbrev": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
-      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
-      "license": "ISC"
-    },
-    "node_modules/accepts": {
-      "version": "1.3.8",
-      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
-      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
-      "license": "MIT",
-      "dependencies": {
-        "mime-types": "~2.1.34",
-        "negotiator": "0.6.3"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/agent-base": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
-      "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
-      "license": "MIT",
-      "dependencies": {
-        "debug": "4"
-      },
-      "engines": {
-        "node": ">= 6.0.0"
-      }
-    },
-    "node_modules/agent-base/node_modules/debug": {
-      "version": "4.4.3",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
-      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
-      "license": "MIT",
-      "dependencies": {
-        "ms": "^2.1.3"
-      },
-      "engines": {
-        "node": ">=6.0"
-      },
-      "peerDependenciesMeta": {
-        "supports-color": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/agent-base/node_modules/ms": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "license": "MIT"
-    },
-    "node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/aproba": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
-      "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==",
-      "license": "ISC"
-    },
-    "node_modules/are-we-there-yet": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
-      "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
-      "deprecated": "This package is no longer supported.",
-      "license": "ISC",
-      "dependencies": {
-        "delegates": "^1.0.0",
-        "readable-stream": "^3.6.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/array-flatten": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
-      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
-      "license": "MIT"
-    },
-    "node_modules/balanced-match": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
-      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
-      "license": "MIT"
-    },
-    "node_modules/bcrypt": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
-      "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "dependencies": {
-        "@mapbox/node-pre-gyp": "^1.0.11",
-        "node-addon-api": "^5.0.0"
-      },
-      "engines": {
-        "node": ">= 10.0.0"
-      }
-    },
-    "node_modules/bcryptjs": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
-      "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
-      "license": "BSD-3-Clause",
-      "bin": {
-        "bcrypt": "bin/bcrypt"
-      }
-    },
-    "node_modules/body-parser": {
-      "version": "1.20.4",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
-      "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
-      "license": "MIT",
-      "dependencies": {
-        "bytes": "~3.1.2",
-        "content-type": "~1.0.5",
-        "debug": "2.6.9",
-        "depd": "2.0.0",
-        "destroy": "~1.2.0",
-        "http-errors": "~2.0.1",
-        "iconv-lite": "~0.4.24",
-        "on-finished": "~2.4.1",
-        "qs": "~6.14.0",
-        "raw-body": "~2.5.3",
-        "type-is": "~1.6.18",
-        "unpipe": "~1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.8",
-        "npm": "1.2.8000 || >= 1.4.16"
-      }
-    },
-    "node_modules/brace-expansion": {
-      "version": "1.1.13",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
-      "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
-      "license": "MIT",
-      "dependencies": {
-        "balanced-match": "^1.0.0",
-        "concat-map": "0.0.1"
-      }
-    },
-    "node_modules/bytes": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
-      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/call-bind-apply-helpers": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
-      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
-      "license": "MIT",
-      "dependencies": {
-        "es-errors": "^1.3.0",
-        "function-bind": "^1.1.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/call-bound": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
-      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
-      "license": "MIT",
-      "dependencies": {
-        "call-bind-apply-helpers": "^1.0.2",
-        "get-intrinsic": "^1.3.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/chownr": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
-      "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/color-support": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
-      "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
-      "license": "ISC",
-      "bin": {
-        "color-support": "bin.js"
-      }
-    },
-    "node_modules/concat-map": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
-      "license": "MIT"
-    },
-    "node_modules/console-control-strings": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-      "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
-      "license": "ISC"
-    },
-    "node_modules/content-disposition": {
-      "version": "0.5.4",
-      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
-      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
-      "license": "MIT",
-      "dependencies": {
-        "safe-buffer": "5.2.1"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/content-type": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
-      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/cookie": {
-      "version": "0.7.2",
-      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
-      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/cookie-signature": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
-      "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
-      "license": "MIT"
-    },
-    "node_modules/cors": {
-      "version": "2.8.6",
-      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
-      "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
-      "license": "MIT",
-      "dependencies": {
-        "object-assign": "^4",
-        "vary": "^1"
-      },
-      "engines": {
-        "node": ">= 0.10"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/express"
-      }
-    },
-    "node_modules/debug": {
-      "version": "2.6.9",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-      "license": "MIT",
-      "dependencies": {
-        "ms": "2.0.0"
-      }
-    },
-    "node_modules/delegates": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-      "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
-      "license": "MIT"
-    },
-    "node_modules/depd": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
-      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/destroy": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
-      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.8",
-        "npm": "1.2.8000 || >= 1.4.16"
-      }
-    },
-    "node_modules/detect-libc": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
-      "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
-      "license": "Apache-2.0",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/dotenv": {
-      "version": "17.3.1",
-      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
-      "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
-      "license": "BSD-2-Clause",
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://dotenvx.com"
-      }
-    },
-    "node_modules/dunder-proto": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
-      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
-      "license": "MIT",
-      "dependencies": {
-        "call-bind-apply-helpers": "^1.0.1",
-        "es-errors": "^1.3.0",
-        "gopd": "^1.2.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/ee-first": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
-      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
-      "license": "MIT"
-    },
-    "node_modules/emoji-regex": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "license": "MIT"
-    },
-    "node_modules/encodeurl": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
-      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/es-define-property": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
-      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/es-errors": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
-      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/es-object-atoms": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
-      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
-      "license": "MIT",
-      "dependencies": {
-        "es-errors": "^1.3.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/escape-html": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
-      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
-      "license": "MIT"
-    },
-    "node_modules/etag": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
-      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/express": {
-      "version": "4.22.1",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
-      "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
-      "license": "MIT",
-      "dependencies": {
-        "accepts": "~1.3.8",
-        "array-flatten": "1.1.1",
-        "body-parser": "~1.20.3",
-        "content-disposition": "~0.5.4",
-        "content-type": "~1.0.4",
-        "cookie": "~0.7.1",
-        "cookie-signature": "~1.0.6",
-        "debug": "2.6.9",
-        "depd": "2.0.0",
-        "encodeurl": "~2.0.0",
-        "escape-html": "~1.0.3",
-        "etag": "~1.8.1",
-        "finalhandler": "~1.3.1",
-        "fresh": "~0.5.2",
-        "http-errors": "~2.0.0",
-        "merge-descriptors": "1.0.3",
-        "methods": "~1.1.2",
-        "on-finished": "~2.4.1",
-        "parseurl": "~1.3.3",
-        "path-to-regexp": "~0.1.12",
-        "proxy-addr": "~2.0.7",
-        "qs": "~6.14.0",
-        "range-parser": "~1.2.1",
-        "safe-buffer": "5.2.1",
-        "send": "~0.19.0",
-        "serve-static": "~1.16.2",
-        "setprototypeof": "1.2.0",
-        "statuses": "~2.0.1",
-        "type-is": "~1.6.18",
-        "utils-merge": "1.0.1",
-        "vary": "~1.1.2"
-      },
-      "engines": {
-        "node": ">= 0.10.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/express"
-      }
-    },
-    "node_modules/express-session": {
-      "version": "1.19.0",
-      "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz",
-      "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==",
-      "license": "MIT",
-      "dependencies": {
-        "cookie": "~0.7.2",
-        "cookie-signature": "~1.0.7",
-        "debug": "~2.6.9",
-        "depd": "~2.0.0",
-        "on-headers": "~1.1.0",
-        "parseurl": "~1.3.3",
-        "safe-buffer": "~5.2.1",
-        "uid-safe": "~2.1.5"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/express"
-      }
-    },
-    "node_modules/finalhandler": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
-      "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
-      "license": "MIT",
-      "dependencies": {
-        "debug": "2.6.9",
-        "encodeurl": "~2.0.0",
-        "escape-html": "~1.0.3",
-        "on-finished": "~2.4.1",
-        "parseurl": "~1.3.3",
-        "statuses": "~2.0.2",
-        "unpipe": "~1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/forwarded": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
-      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/fresh": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
-      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/fs-minipass": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
-      "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
-      "license": "ISC",
-      "dependencies": {
-        "minipass": "^3.0.0"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/fs-minipass/node_modules/minipass": {
-      "version": "3.3.6",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
-      "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
-      "license": "ISC",
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/fs.realpath": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
-      "license": "ISC"
-    },
-    "node_modules/function-bind": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
-      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
-      "license": "MIT",
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/gauge": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
-      "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
-      "deprecated": "This package is no longer supported.",
-      "license": "ISC",
-      "dependencies": {
-        "aproba": "^1.0.3 || ^2.0.0",
-        "color-support": "^1.1.2",
-        "console-control-strings": "^1.0.0",
-        "has-unicode": "^2.0.1",
-        "object-assign": "^4.1.1",
-        "signal-exit": "^3.0.0",
-        "string-width": "^4.2.3",
-        "strip-ansi": "^6.0.1",
-        "wide-align": "^1.1.2"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/get-intrinsic": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
-      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
-      "license": "MIT",
-      "dependencies": {
-        "call-bind-apply-helpers": "^1.0.2",
-        "es-define-property": "^1.0.1",
-        "es-errors": "^1.3.0",
-        "es-object-atoms": "^1.1.1",
-        "function-bind": "^1.1.2",
-        "get-proto": "^1.0.1",
-        "gopd": "^1.2.0",
-        "has-symbols": "^1.1.0",
-        "hasown": "^2.0.2",
-        "math-intrinsics": "^1.1.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/get-proto": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
-      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
-      "license": "MIT",
-      "dependencies": {
-        "dunder-proto": "^1.0.1",
-        "es-object-atoms": "^1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/glob": {
-      "version": "7.2.3",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
-      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
-      "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
-      "license": "ISC",
-      "dependencies": {
-        "fs.realpath": "^1.0.0",
-        "inflight": "^1.0.4",
-        "inherits": "2",
-        "minimatch": "^3.1.1",
-        "once": "^1.3.0",
-        "path-is-absolute": "^1.0.0"
-      },
-      "engines": {
-        "node": "*"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
-    "node_modules/gopd": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
-      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/has-symbols": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
-      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/has-unicode": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
-      "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
-      "license": "ISC"
-    },
-    "node_modules/hasown": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
-      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
-      "license": "MIT",
-      "dependencies": {
-        "function-bind": "^1.1.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/http-errors": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
-      "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
-      "license": "MIT",
-      "dependencies": {
-        "depd": "~2.0.0",
-        "inherits": "~2.0.4",
-        "setprototypeof": "~1.2.0",
-        "statuses": "~2.0.2",
-        "toidentifier": "~1.0.1"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/express"
-      }
-    },
-    "node_modules/https-proxy-agent": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
-      "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
-      "license": "MIT",
-      "dependencies": {
-        "agent-base": "6",
-        "debug": "4"
-      },
-      "engines": {
-        "node": ">= 6"
-      }
-    },
-    "node_modules/https-proxy-agent/node_modules/debug": {
-      "version": "4.4.3",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
-      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
-      "license": "MIT",
-      "dependencies": {
-        "ms": "^2.1.3"
-      },
-      "engines": {
-        "node": ">=6.0"
-      },
-      "peerDependenciesMeta": {
-        "supports-color": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/https-proxy-agent/node_modules/ms": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "license": "MIT"
-    },
-    "node_modules/iconv-lite": {
-      "version": "0.4.24",
-      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
-      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
-      "license": "MIT",
-      "dependencies": {
-        "safer-buffer": ">= 2.1.2 < 3"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/inflight": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
-      "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
-      "license": "ISC",
-      "dependencies": {
-        "once": "^1.3.0",
-        "wrappy": "1"
-      }
-    },
-    "node_modules/inherits": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-      "license": "ISC"
-    },
-    "node_modules/ipaddr.js": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
-      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.10"
-      }
-    },
-    "node_modules/is-fullwidth-code-point": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/make-dir": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
-      "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
-      "license": "MIT",
-      "dependencies": {
-        "semver": "^6.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/make-dir/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
-    "node_modules/math-intrinsics": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
-      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/media-typer": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
-      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/merge-descriptors": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
-      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
-      "license": "MIT",
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/methods": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
-      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/mime": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
-      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
-      "license": "MIT",
-      "bin": {
-        "mime": "cli.js"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/mime-db": {
-      "version": "1.52.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
-      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/mime-types": {
-      "version": "2.1.35",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
-      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "license": "MIT",
-      "dependencies": {
-        "mime-db": "1.52.0"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/minimatch": {
-      "version": "3.1.5",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
-      "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
-      "license": "ISC",
-      "dependencies": {
-        "brace-expansion": "^1.1.7"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
-    "node_modules/minizlib": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
-      "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
-      "license": "MIT",
-      "dependencies": {
-        "minipass": "^3.0.0",
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/minizlib/node_modules/minipass": {
-      "version": "3.3.6",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
-      "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
-      "license": "ISC",
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/mkdirp": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
-      "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
-      "license": "MIT",
-      "bin": {
-        "mkdirp": "bin/cmd.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/ms": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
-      "license": "MIT"
-    },
-    "node_modules/negotiator": {
-      "version": "0.6.3",
-      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
-      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/node-addon-api": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
-      "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
-      "license": "MIT"
-    },
-    "node_modules/node-fetch": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
-      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
-      "license": "MIT",
-      "dependencies": {
-        "whatwg-url": "^5.0.0"
-      },
-      "engines": {
-        "node": "4.x || >=6.0.0"
-      },
-      "peerDependencies": {
-        "encoding": "^0.1.0"
-      },
-      "peerDependenciesMeta": {
-        "encoding": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/nodemailer": {
-      "version": "6.10.1",
-      "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz",
-      "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==",
-      "license": "MIT-0",
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/nopt": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
-      "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
-      "license": "ISC",
-      "dependencies": {
-        "abbrev": "1"
-      },
-      "bin": {
-        "nopt": "bin/nopt.js"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/npmlog": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
-      "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
-      "deprecated": "This package is no longer supported.",
-      "license": "ISC",
-      "dependencies": {
-        "are-we-there-yet": "^2.0.0",
-        "console-control-strings": "^1.1.0",
-        "gauge": "^3.0.0",
-        "set-blocking": "^2.0.0"
-      }
-    },
-    "node_modules/object-assign": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/object-inspect": {
-      "version": "1.13.4",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
-      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/on-finished": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
-      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
-      "license": "MIT",
-      "dependencies": {
-        "ee-first": "1.1.1"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/on-headers": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
-      "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/once": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
-      "license": "ISC",
-      "dependencies": {
-        "wrappy": "1"
-      }
-    },
-    "node_modules/parseurl": {
-      "version": "1.3.3",
-      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
-      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/path-is-absolute": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/path-to-regexp": {
-      "version": "0.1.13",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
-      "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
-      "license": "MIT"
-    },
-    "node_modules/pg": {
-      "version": "8.20.0",
-      "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
-      "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
-      "license": "MIT",
-      "dependencies": {
-        "pg-connection-string": "^2.12.0",
-        "pg-pool": "^3.13.0",
-        "pg-protocol": "^1.13.0",
-        "pg-types": "2.2.0",
-        "pgpass": "1.0.5"
-      },
-      "engines": {
-        "node": ">= 16.0.0"
-      },
-      "optionalDependencies": {
-        "pg-cloudflare": "^1.3.0"
-      },
-      "peerDependencies": {
-        "pg-native": ">=3.0.1"
-      },
-      "peerDependenciesMeta": {
-        "pg-native": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/pg-cloudflare": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
-      "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
-      "license": "MIT",
-      "optional": true
-    },
-    "node_modules/pg-connection-string": {
-      "version": "2.12.0",
-      "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz",
-      "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==",
-      "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.13.0",
-      "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz",
-      "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==",
-      "license": "MIT",
-      "peerDependencies": {
-        "pg": ">=8.0"
-      }
-    },
-    "node_modules/pg-protocol": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
-      "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
-      "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/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/proxy-addr": {
-      "version": "2.0.7",
-      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
-      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
-      "license": "MIT",
-      "dependencies": {
-        "forwarded": "0.2.0",
-        "ipaddr.js": "1.9.1"
-      },
-      "engines": {
-        "node": ">= 0.10"
-      }
-    },
-    "node_modules/qs": {
-      "version": "6.14.2",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
-      "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "side-channel": "^1.1.0"
-      },
-      "engines": {
-        "node": ">=0.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/random-bytes": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
-      "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/range-parser": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
-      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/raw-body": {
-      "version": "2.5.3",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
-      "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
-      "license": "MIT",
-      "dependencies": {
-        "bytes": "~3.1.2",
-        "http-errors": "~2.0.1",
-        "iconv-lite": "~0.4.24",
-        "unpipe": "~1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/readable-stream": {
-      "version": "3.6.2",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
-      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
-      "license": "MIT",
-      "dependencies": {
-        "inherits": "^2.0.3",
-        "string_decoder": "^1.1.1",
-        "util-deprecate": "^1.0.1"
-      },
-      "engines": {
-        "node": ">= 6"
-      }
-    },
-    "node_modules/rimraf": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
-      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
-      "deprecated": "Rimraf versions prior to v4 are no longer supported",
-      "license": "ISC",
-      "dependencies": {
-        "glob": "^7.1.3"
-      },
-      "bin": {
-        "rimraf": "bin.js"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
-    "node_modules/safe-buffer": {
-      "version": "5.2.1",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ],
-      "license": "MIT"
-    },
-    "node_modules/safer-buffer": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-      "license": "MIT"
-    },
-    "node_modules/semver": {
-      "version": "7.7.4",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
-      "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/send": {
-      "version": "0.19.2",
-      "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
-      "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
-      "license": "MIT",
-      "dependencies": {
-        "debug": "2.6.9",
-        "depd": "2.0.0",
-        "destroy": "1.2.0",
-        "encodeurl": "~2.0.0",
-        "escape-html": "~1.0.3",
-        "etag": "~1.8.1",
-        "fresh": "~0.5.2",
-        "http-errors": "~2.0.1",
-        "mime": "1.6.0",
-        "ms": "2.1.3",
-        "on-finished": "~2.4.1",
-        "range-parser": "~1.2.1",
-        "statuses": "~2.0.2"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/send/node_modules/ms": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "license": "MIT"
-    },
-    "node_modules/serve-static": {
-      "version": "1.16.3",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
-      "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
-      "license": "MIT",
-      "dependencies": {
-        "encodeurl": "~2.0.0",
-        "escape-html": "~1.0.3",
-        "parseurl": "~1.3.3",
-        "send": "~0.19.1"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/set-blocking": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
-      "license": "ISC"
-    },
-    "node_modules/setprototypeof": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
-      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
-      "license": "ISC"
-    },
-    "node_modules/side-channel": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
-      "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
-      "license": "MIT",
-      "dependencies": {
-        "es-errors": "^1.3.0",
-        "object-inspect": "^1.13.3",
-        "side-channel-list": "^1.0.0",
-        "side-channel-map": "^1.0.1",
-        "side-channel-weakmap": "^1.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/side-channel-list": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
-      "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
-      "license": "MIT",
-      "dependencies": {
-        "es-errors": "^1.3.0",
-        "object-inspect": "^1.13.3"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/side-channel-map": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
-      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
-      "license": "MIT",
-      "dependencies": {
-        "call-bound": "^1.0.2",
-        "es-errors": "^1.3.0",
-        "get-intrinsic": "^1.2.5",
-        "object-inspect": "^1.13.3"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/side-channel-weakmap": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
-      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
-      "license": "MIT",
-      "dependencies": {
-        "call-bound": "^1.0.2",
-        "es-errors": "^1.3.0",
-        "get-intrinsic": "^1.2.5",
-        "object-inspect": "^1.13.3",
-        "side-channel-map": "^1.0.1"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/signal-exit": {
-      "version": "3.0.7",
-      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
-      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
-      "license": "ISC"
-    },
-    "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/statuses": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
-      "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/string_decoder": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
-      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
-      "license": "MIT",
-      "dependencies": {
-        "safe-buffer": "~5.2.0"
-      }
-    },
-    "node_modules/string-width": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "license": "MIT",
-      "dependencies": {
-        "emoji-regex": "^8.0.0",
-        "is-fullwidth-code-point": "^3.0.0",
-        "strip-ansi": "^6.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "license": "MIT",
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/tar": {
-      "version": "6.2.1",
-      "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
-      "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
-      "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
-      "license": "ISC",
-      "dependencies": {
-        "chownr": "^2.0.0",
-        "fs-minipass": "^2.0.0",
-        "minipass": "^5.0.0",
-        "minizlib": "^2.1.1",
-        "mkdirp": "^1.0.3",
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/tar/node_modules/minipass": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
-      "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/toidentifier": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
-      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.6"
-      }
-    },
-    "node_modules/tr46": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
-      "license": "MIT"
-    },
-    "node_modules/type-is": {
-      "version": "1.6.18",
-      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
-      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
-      "license": "MIT",
-      "dependencies": {
-        "media-typer": "0.3.0",
-        "mime-types": "~2.1.24"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/uid-safe": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
-      "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
-      "license": "MIT",
-      "dependencies": {
-        "random-bytes": "~1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/unpipe": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
-      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/util-deprecate": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
-      "license": "MIT"
-    },
-    "node_modules/utils-merge": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
-      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4.0"
-      }
-    },
-    "node_modules/vary": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
-      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/webidl-conversions": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
-      "license": "BSD-2-Clause"
-    },
-    "node_modules/whatwg-url": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
-      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
-      "license": "MIT",
-      "dependencies": {
-        "tr46": "~0.0.3",
-        "webidl-conversions": "^3.0.0"
-      }
-    },
-    "node_modules/wide-align": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
-      "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
-      "license": "ISC",
-      "dependencies": {
-        "string-width": "^1.0.2 || 2 || 3 || 4"
-      }
-    },
-    "node_modules/wrappy": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
-      "license": "ISC"
-    },
-    "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": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "license": "ISC"
-    }
-  }
-}
Index: ckage.json
===================================================================
--- package.json	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,19 +1,0 @@
-{
-  "name": "wedding-planner",
-  "version": "1.0.0",
-  "description": "Wedding Planner DB Project - Phase P4",
-  "main": "server.js",
-  "scripts": {
-    "start": "node server.js"
-  },
-  "dependencies": {
-    "bcrypt": "^5.1.1",
-    "bcryptjs": "^3.0.3",
-    "cors": "^2.8.5",
-    "dotenv": "^17.3.1",
-    "express": "^4.18.2",
-    "express-session": "^1.17.3",
-    "nodemailer": "^6.9.9",
-    "pg": "^8.11.3"
-  }
-}
Index: vp.html
===================================================================
--- rsvp.html	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,443 +1,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <title>Wedding RSVP</title>
-    <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet"/>
-    <style>
-        :root {
-            --cream:#fdf8f3; --cream-2:#f9f1e8;
-            --blush-light:#fce8e8; --blush-mid:#e8949c;
-            --rose:#d4606a; --rose-deep:#b84550;
-            --gold:#c9a84c; --gold-dark:#9e7a28;
-            --text-dark:#2c1f1f; --text-mid:#6b4f4f; --text-light:#a08080;
-            --border:#ecddd0; --border-light:#f4ebe2;
-            --green:#3a9e6b; --green-light:#e4f5ec;
-            --red:#c94545; --red-light:#fdeaea;
-            --shadow-lg:0 16px 40px rgba(44,31,31,.14),0 4px 12px rgba(44,31,31,.08);
-            --radius-md:10px; --radius-lg:16px; --radius-xl:22px;
-            --transition:200ms cubic-bezier(.4,0,.2,1);
-        }
-        *,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
-        html,body{
-            height:100%; font-family:'DM Sans',sans-serif;
-            background:linear-gradient(135deg,#fdf8f3,#f9f1e8,#fce8e8);
-            color:var(--text-dark); -webkit-font-smoothing:antialiased;
-            display:flex; align-items:center; justify-content:center;
-            min-height:100vh; padding:20px;
-        }
-
-        .rsvp-wrapper { width:100%; max-width:520px; }
-
-        /* logo */
-        .rsvp-logo {
-            text-align:center; margin-bottom:24px;
-        }
-        .rsvp-logo .mark {
-            width:48px; height:48px; margin:0 auto 10px;
-            background:linear-gradient(135deg,var(--rose),var(--blush-mid));
-            border-radius:12px;
-            display:flex; align-items:center; justify-content:center;
-            box-shadow:0 4px 16px rgba(212,96,106,.35);
-        }
-        .rsvp-logo .mark svg { width:24px; height:24px; }
-        .rsvp-logo h1 {
-            font-family:'Cormorant Garamond',serif;
-            font-size:24px; font-weight:700;
-        }
-
-        /* card */
-        .rsvp-card {
-            background:#fff; border-radius:var(--radius-xl);
-            border:1px solid var(--border-light);
-            box-shadow:var(--shadow-lg); overflow:hidden;
-        }
-
-        /* header band */
-        .rsvp-header {
-            background:linear-gradient(135deg,var(--rose),var(--blush-mid));
-            padding:28px 32px; text-align:center; color:#fff;
-        }
-        .rsvp-header .emoji { font-size:40px; margin-bottom:8px; }
-        .rsvp-header h2 {
-            font-family:'Cormorant Garamond',serif;
-            font-size:26px; font-weight:700; margin-bottom:4px;
-        }
-        .rsvp-header p { font-size:14px; opacity:.9; }
-
-        /* body */
-        .rsvp-body { padding:32px; }
-
-        .gold-rule {
-            width:36px; height:2px;
-            background:linear-gradient(90deg,var(--gold),transparent);
-            border-radius:2px; margin:0 0 20px;
-        }
-
-        /* event info box */
-        .event-box {
-            background:var(--cream); border:1px solid var(--border-light);
-            border-radius:var(--radius-lg); padding:18px 20px;
-            margin-bottom:24px;
-        }
-        .event-box .ev-label {
-            font-size:10px; font-weight:700; text-transform:uppercase;
-            letter-spacing:.1em; color:var(--text-light); margin-bottom:6px;
-        }
-        .event-box .ev-type {
-            font-family:'Cormorant Garamond',serif;
-            font-size:22px; font-weight:700; color:var(--text-dark);
-            margin-bottom:6px;
-        }
-        .event-box .ev-meta {
-            font-size:13px; color:var(--text-mid);
-            display:flex; gap:16px; flex-wrap:wrap;
-        }
-        .event-box .ev-meta span { display:flex; align-items:center; gap:5px; }
-
-        /* guest greeting */
-        .guest-greeting {
-            font-size:15px; color:var(--text-mid);
-            margin-bottom:24px; line-height:1.6;
-        }
-        .guest-greeting strong { color:var(--text-dark); }
-
-        /* RSVP buttons */
-        .rsvp-actions {
-            display:grid; grid-template-columns:1fr 1fr; gap:14px;
-            margin-bottom:20px;
-        }
-        .rsvp-btn {
-            padding:16px 12px; border-radius:var(--radius-lg);
-            border:2px solid transparent; cursor:pointer;
-            font-family:'DM Sans',sans-serif; font-size:15px; font-weight:600;
-            display:flex; flex-direction:column; align-items:center; gap:6px;
-            transition:all var(--transition);
-        }
-        .rsvp-btn .btn-icon { font-size:28px; }
-        .rsvp-btn-accept {
-            background:var(--green-light); color:var(--green);
-            border-color:#b2e0ca;
-        }
-        .rsvp-btn-accept:hover {
-            background:var(--green); color:#fff; border-color:var(--green);
-            transform:translateY(-2px); box-shadow:0 6px 20px rgba(58,158,107,.3);
-        }
-        .rsvp-btn-decline {
-            background:var(--red-light); color:var(--red);
-            border-color:#f5c2c2;
-        }
-        .rsvp-btn-decline:hover {
-            background:var(--red); color:#fff; border-color:var(--red);
-            transform:translateY(-2px); box-shadow:0 6px 20px rgba(201,69,69,.3);
-        }
-        .rsvp-btn:disabled {
-            opacity:.5; cursor:not-allowed; transform:none !important;
-        }
-        .rsvp-btn.selected-accept {
-            background:var(--green); color:#fff; border-color:var(--green);
-        }
-        .rsvp-btn.selected-decline {
-            background:var(--red); color:#fff; border-color:var(--red);
-        }
-
-        /* result state */
-        .rsvp-result {
-            display:none; text-align:center; padding:24px;
-        }
-        .rsvp-result.show { display:block; }
-        .rsvp-result .result-emoji { font-size:56px; margin-bottom:12px; }
-        .rsvp-result h3 {
-            font-family:'Cormorant Garamond',serif;
-            font-size:24px; font-weight:700; margin-bottom:8px;
-        }
-        .rsvp-result p { font-size:14px; color:var(--text-mid); line-height:1.6; }
-        .result-accepted h3 { color:var(--green); }
-        .result-declined h3 { color:var(--red); }
-
-        /* loading state */
-        .rsvp-loading {
-            display:none; text-align:center; padding:32px;
-        }
-        .rsvp-loading.show { display:block; }
-        .spinner {
-            width:36px; height:36px; margin:0 auto 12px;
-            border:3px solid var(--border);
-            border-top-color:var(--rose); border-radius:50%;
-            animation:spin .7s linear infinite;
-        }
-        @keyframes spin { to { transform:rotate(360deg); } }
-
-        /* error state */
-        .rsvp-error {
-            display:none; text-align:center; padding:24px;
-        }
-        .rsvp-error.show { display:block; }
-        .rsvp-error .error-emoji { font-size:48px; margin-bottom:12px; }
-        .rsvp-error h3 {
-            font-family:'Cormorant Garamond',serif;
-            font-size:22px; color:var(--red); margin-bottom:8px;
-        }
-        .rsvp-error p { font-size:13px; color:var(--text-mid); }
-
-        /* footer */
-        .rsvp-footer {
-            background:var(--cream); border-top:1px solid var(--border-light);
-            padding:14px 32px; text-align:center;
-            font-size:12px; color:var(--text-light);
-        }
-
-        /* already responded badge */
-        .already-badge {
-            display:inline-flex; align-items:center; gap:6px;
-            padding:6px 14px; border-radius:999px;
-            font-size:12px; font-weight:600; margin-bottom:16px;
-        }
-        .already-accepted { background:var(--green-light); color:var(--green); }
-        .already-declined { background:var(--red-light); color:var(--red); }
-        .already-pending  { background:#fff4e0; color:#c87f2a; }
-    </style>
-</head>
-<body>
-
-<div class="rsvp-wrapper">
-
-    <!-- Logo -->
-    <div class="rsvp-logo">
-        <div class="mark">
-            <svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
-                <path d="M12 21.593c-5.63-5.539-11-10.297-11-14.402C1 3.402 4.248 1 7.5 1 9.849 1 11.271 2.007 12 3.011 12.729 2.007 14.151 1 16.5 1 19.752 1 23 3.402 23 7.191c0 4.105-5.37 8.863-11 14.402z"/>
-            </svg>
-        </div>
-        <h1>Wedding Planner</h1>
-    </div>
-
-    <!-- Card -->
-    <div class="rsvp-card">
-
-        <!-- Header -->
-        <div class="rsvp-header">
-            <div class="emoji">💌</div>
-            <h2>Wedding Invitation</h2>
-            <p>You have been invited — please let us know if you can attend</p>
-        </div>
-
-        <!-- Loading -->
-        <div class="rsvp-loading" id="loadingState">
-            <div class="rsvp-body">
-                <div class="spinner"></div>
-                <p style="color:var(--text-light);font-size:14px;">Loading your invitation...</p>
-            </div>
-        </div>
-
-        <!-- Error -->
-        <div class="rsvp-error" id="errorState">
-            <div class="rsvp-body">
-                <div class="error-emoji">⚠️</div>
-                <h3>Invalid Link</h3>
-                <p id="errorMsg">This RSVP link is invalid or has expired. Please contact the wedding organizer.</p>
-            </div>
-        </div>
-
-        <!-- Main RSVP form -->
-        <div class="rsvp-body" id="rsvpForm" style="display:none;">
-            <div class="gold-rule"></div>
-
-            <!-- existing response badge -->
-            <div id="alreadyBadge" style="display:none;"></div>
-
-            <!-- guest greeting -->
-            <div class="guest-greeting" id="guestGreeting"></div>
-
-            <!-- event info -->
-            <div class="event-box" id="eventBox"></div>
-
-            <!-- action buttons -->
-            <div class="rsvp-actions" id="rsvpActions">
-                <button class="rsvp-btn rsvp-btn-accept" id="btnAccept" onclick="submitRsvp('accepted')">
-                    <span class="btn-icon">✅</span>
-                    <span>I'll be there!</span>
-                </button>
-                <button class="rsvp-btn rsvp-btn-decline" id="btnDecline" onclick="submitRsvp('declined')">
-                    <span class="btn-icon">❌</span>
-                    <span>Can't make it</span>
-                </button>
-            </div>
-
-            <p style="font-size:12px;color:var(--text-light);text-align:center;">
-                You can change your response at any time by using the same link from your email.
-            </p>
-        </div>
-
-        <!-- Result after submitting -->
-        <div class="rsvp-result" id="rsvpResult">
-            <div class="rsvp-body">
-                <div class="result-emoji" id="resultEmoji"></div>
-                <h3 id="resultTitle"></h3>
-                <p id="resultMsg"></p>
-            </div>
-        </div>
-
-        <div class="rsvp-footer">
-            Wedding Planner · Sent with love 💌
-        </div>
-    </div>
-
-</div>
-
-<script>
-    const API = 'http://localhost:3000';
-
-    // ── Parse URL params ────────────────────────────────────
-    const params  = new URLSearchParams(window.location.search);
-    const guestId = params.get('guest');
-    const eventId = params.get('event');
-    const token   = params.get('token');
-    const action  = params.get('action'); // pre-filled from email link
-
-    // ── State ────────────────────────────────────────────────
-    let guestData = null;
-    let existingRsvp = null;
-
-    // ── On load ──────────────────────────────────────────────
-    window.addEventListener('DOMContentLoaded', async () => {
-        if (!guestId || !eventId || !token) {
-            showError('Missing required parameters. Please use the link from your email.');
-            return;
-        }
-        showLoading(true);
-        await loadInvitation();
-    });
-
-    async function loadInvitation() {
-        try {
-            // Check for existing RSVP
-            const rsvpRes = await fetch(`${API}/api/rsvp?guest_id=${guestId}&event_id=${eventId}`);
-            if (rsvpRes.ok) {
-                existingRsvp = await rsvpRes.json();
-            }
-
-            // We'll get guest + event info from the RSVP endpoint by doing a dry run
-            // For now build the form and auto-submit if action is in URL
-            showLoading(false);
-            showForm();
-
-            // Auto-submit if action came from email link
-            if (action === 'accepted' || action === 'declined') {
-                await submitRsvp(action, true);
-            }
-
-        } catch (err) {
-            showError('Could not load invitation. Please check your connection.');
-        }
-    }
-
-    // ── Submit RSVP ──────────────────────────────────────────
-    async function submitRsvp(status, auto = false) {
-        disableButtons();
-        if (!auto) showLoading(true);
-
-        try {
-            const res = await fetch(`${API}/api/rsvp`, {
-                method: 'POST',
-                headers: { 'Content-Type': 'application/json' },
-                body: JSON.stringify({
-                    guest_id: guestId,
-                    event_id: eventId,
-                    token,
-                    status
-                })
-            });
-
-            const data = await res.json();
-            showLoading(false);
-
-            if (!res.ok) {
-                showError(data.error || 'Could not submit your response.');
-                return;
-            }
-
-            // Show result
-            const guest = data.guest;
-            const accepted = status === 'accepted';
-            showResult({
-                emoji:   accepted ? '🎉' : '😢',
-                title:   accepted ? 'See you there!' : 'We\'ll miss you!',
-                msg:     accepted
-                    ? `Thank you, ${guest ? guest.first_name : 'Guest'}! Your attendance for <strong>${guest ? guest.event_type : 'the event'}</strong> on <strong>${guest ? guest.date : ''}</strong> has been confirmed. We can't wait to celebrate with you! 🥂`
-                    : `Thank you for letting us know, ${guest ? guest.first_name : 'Guest'}. We're sorry you won't be able to make it to <strong>${guest ? guest.event_type : 'the event'}</strong>, but we appreciate your response.`,
-                type:    accepted ? 'accepted' : 'declined'
-            });
-
-        } catch (err) {
-            showLoading(false);
-            showError('Connection error. Please try again.');
-        }
-    }
-
-    // ── UI Helpers ───────────────────────────────────────────
-    function showLoading(show) {
-        document.getElementById('loadingState').classList.toggle('show', show);
-        document.getElementById('rsvpForm').style.display = show ? 'none' : 'block';
-    }
-
-    function showForm() {
-        document.getElementById('loadingState').classList.remove('show');
-        document.getElementById('rsvpForm').style.display = 'block';
-
-        // Show greeting
-        document.getElementById('guestGreeting').innerHTML =
-            `Dear <strong>Guest</strong>,<br>You have been invited to a special event. Please confirm your attendance below.`;
-
-        // Show event info placeholder (will be filled after submit response)
-        document.getElementById('eventBox').innerHTML = `
-            <div class="ev-label">Event Details</div>
-            <div class="ev-type">Wedding Event</div>
-            <div class="ev-meta">
-                <span>📅 Check your email for date details</span>
-            </div>`;
-
-        // Show existing response badge
-        if (existingRsvp) {
-            const badge = document.getElementById('alreadyBadge');
-            badge.style.display = 'block';
-            const cls = existingRsvp.status === 'accepted' ? 'already-accepted'
-                      : existingRsvp.status === 'declined' ? 'already-declined'
-                      : 'already-pending';
-            const icon = existingRsvp.status === 'accepted' ? '✅'
-                       : existingRsvp.status === 'declined' ? '❌' : '⏳';
-            badge.innerHTML = `
-                <span class="already-badge ${cls}">
-                    ${icon} You previously responded: <strong>${existingRsvp.status}</strong>
-                    &nbsp;— you can change your response below
-                </span>`;
-        }
-    }
-
-    function showError(msg) {
-        document.getElementById('loadingState').classList.remove('show');
-        document.getElementById('rsvpForm').style.display = 'none';
-        document.getElementById('rsvpResult').classList.remove('show');
-        const err = document.getElementById('errorState');
-        err.classList.add('show');
-        document.getElementById('errorMsg').textContent = msg;
-    }
-
-    function showResult({ emoji, title, msg, type }) {
-        document.getElementById('rsvpForm').style.display = 'none';
-        document.getElementById('loadingState').classList.remove('show');
-        const result = document.getElementById('rsvpResult');
-        result.className = `rsvp-result show result-${type}`;
-        document.getElementById('resultEmoji').textContent = emoji;
-        document.getElementById('resultTitle').textContent = title;
-        document.getElementById('resultMsg').innerHTML = msg;
-    }
-
-    function disableButtons() {
-        document.getElementById('btnAccept').disabled  = true;
-        document.getElementById('btnDecline').disabled = true;
-    }
-</script>
-</body>
-</html>
Index: hema_creation (1).sql
===================================================================
--- schema_creation (1).sql	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,243 +1,0 @@
--- =============================================================
---  Wedding Planner
--- =============================================================
-
-DROP SCHEMA IF EXISTS project CASCADE;
-CREATE SCHEMA project;
-SET search_path TO project;
-
--- ── Users ──────────────────────────────────────────────────
-CREATE TABLE "user" (
-    user_id        SERIAL PRIMARY KEY,
-    first_name     VARCHAR(50)  NOT NULL,
-    last_name      VARCHAR(50)  NOT NULL,
-    email          VARCHAR(120) NOT NULL UNIQUE,
-    password_hash  VARCHAR(255) NOT NULL DEFAULT '',   -- added for login
-    phone_number   VARCHAR(30),
-    gender         VARCHAR(20),
-    birthday       DATE
-);
-
--- ── Weddings ────────────────────────────────────────────────
-CREATE TABLE wedding (
-    wedding_id SERIAL PRIMARY KEY,
-    "date"     DATE           NOT NULL,
-    budget     NUMERIC(12,2),
-    notes      TEXT,
-    user_id    INTEGER        NOT NULL,
-    CONSTRAINT fk_wedding_user
-        FOREIGN KEY (user_id) REFERENCES "user"(user_id)
-        ON UPDATE CASCADE ON DELETE CASCADE
-);
-
--- ── Churches ────────────────────────────────────────────────
-CREATE TABLE church (
-    church_id  SERIAL PRIMARY KEY,
-    name       VARCHAR(120) NOT NULL,
-    location   VARCHAR(150) NOT NULL,
-    contact    VARCHAR(120) NOT NULL,
-    wedding_id INTEGER UNIQUE,
-    CONSTRAINT fk_church_wedding
-        FOREIGN KEY (wedding_id) REFERENCES wedding(wedding_id)
-        ON UPDATE CASCADE ON DELETE SET NULL
-);
-
--- ── Priests ─────────────────────────────────────────────────
-CREATE TABLE priest (
-    priest_id  SERIAL PRIMARY KEY,
-    name       VARCHAR(120) NOT NULL,
-    contact    VARCHAR(120) NOT NULL,
-    church_id  INTEGER      NOT NULL,
-    CONSTRAINT fk_priest_church
-        FOREIGN KEY (church_id) REFERENCES church(church_id)
-        ON UPDATE CASCADE ON DELETE CASCADE
-);
-
--- ── Church Bookings ─────────────────────────────────────────
-CREATE TABLE church_booking (
-    booking_id SERIAL PRIMARY KEY,
-    "date"     DATE        NOT NULL,
-    start_time TIME        NOT NULL,
-    end_time   TIME        NOT NULL,
-    status     VARCHAR(30) NOT NULL,
-    church_id  INTEGER     NOT NULL,
-    wedding_id INTEGER     NOT NULL,
-    CONSTRAINT fk_cb_church  FOREIGN KEY (church_id)  REFERENCES church(church_id)   ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT fk_cb_wedding FOREIGN KEY (wedding_id) REFERENCES wedding(wedding_id) ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT chk_cb_time   CHECK (end_time > start_time)
-);
-
--- ── Events ──────────────────────────────────────────────────
-CREATE TABLE event (
-    event_id   SERIAL PRIMARY KEY,
-    event_type VARCHAR(60) NOT NULL,
-    "date"     DATE        NOT NULL,
-    start_time TIME        NOT NULL,
-    end_time   TIME        NOT NULL,
-    status     VARCHAR(30) NOT NULL,
-    wedding_id INTEGER     NOT NULL,
-    CONSTRAINT fk_event_wedding
-        FOREIGN KEY (wedding_id) REFERENCES wedding(wedding_id)
-        ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT chk_event_time CHECK (end_time > start_time)
-);
-
--- ── Guests ──────────────────────────────────────────────────
-CREATE TABLE guest (
-    guest_id   SERIAL PRIMARY KEY,
-    first_name VARCHAR(50)  NOT NULL,
-    last_name  VARCHAR(50)  NOT NULL,
-    email      VARCHAR(120),
-    wedding_id INTEGER      NOT NULL,
-    CONSTRAINT fk_guest_wedding
-        FOREIGN KEY (wedding_id) REFERENCES wedding(wedding_id)
-        ON UPDATE CASCADE ON DELETE CASCADE
-);
-
--- ── RSVP ────────────────────────────────────────────────────
-CREATE TABLE event_rsvp (
-    response_id   SERIAL PRIMARY KEY,
-    status        VARCHAR(30) NOT NULL,
-    response_date DATE        NOT NULL,
-    guest_id      INTEGER     NOT NULL,
-    event_id      INTEGER     NOT NULL,
-    CONSTRAINT fk_rsvp_guest  FOREIGN KEY (guest_id)  REFERENCES guest(guest_id)  ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT fk_rsvp_event  FOREIGN KEY (event_id)  REFERENCES event(event_id)  ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT uq_rsvp_guest_event UNIQUE (guest_id, event_id)
-);
-
--- ── Attendance / Seating ─────────────────────────────────────
-CREATE TABLE attendance (
-    attendance_id SERIAL PRIMARY KEY,
-    status        VARCHAR(30) NOT NULL,
-    table_number  INTEGER,
-    role          VARCHAR(40) NOT NULL,
-    guest_id      INTEGER     NOT NULL,
-    event_id      INTEGER     NOT NULL,
-    CONSTRAINT fk_att_guest  FOREIGN KEY (guest_id)  REFERENCES guest(guest_id)  ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT fk_att_event  FOREIGN KEY (event_id)  REFERENCES event(event_id)  ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT uq_att_guest_event UNIQUE (guest_id, event_id),
-    CONSTRAINT chk_table_number CHECK (table_number IS NULL OR table_number > 0)
-);
-
--- ── Venue Types ─────────────────────────────────────────────
-CREATE TABLE venue_type (
-    type_id   SERIAL PRIMARY KEY,
-    type_name VARCHAR(50) NOT NULL UNIQUE
-);
-
--- ── Venues ──────────────────────────────────────────────────
-CREATE TABLE venue (
-    venue_id        SERIAL PRIMARY KEY,
-    name            VARCHAR(120) NOT NULL,
-    location        VARCHAR(150) NOT NULL,
-    city            VARCHAR(80)  NOT NULL,
-    address         VARCHAR(150) NOT NULL,
-    capacity        INTEGER      NOT NULL,
-    menu            TEXT,
-    phone_number    VARCHAR(30),
-    price_per_guest NUMERIC(10,2) NOT NULL,
-    type_id         INTEGER      NOT NULL,
-    CONSTRAINT fk_venue_type     FOREIGN KEY (type_id) REFERENCES venue_type(type_id) ON UPDATE CASCADE ON DELETE RESTRICT,
-    CONSTRAINT chk_capacity      CHECK (capacity > 0),
-    CONSTRAINT chk_price_per_guest CHECK (price_per_guest >= 0)
-);
-
--- ── Venue Bookings ───────────────────────────────────────────
-CREATE TABLE venue_booking (
-    booking_id SERIAL PRIMARY KEY,
-    "date"     DATE          NOT NULL,
-    start_time TIME          NOT NULL,
-    end_time   TIME          NOT NULL,
-    status     VARCHAR(30)   NOT NULL,
-    price      NUMERIC(12,2) NOT NULL,
-    venue_id   INTEGER       NOT NULL,
-    wedding_id INTEGER       NOT NULL,
-    CONSTRAINT fk_vb_venue   FOREIGN KEY (venue_id)   REFERENCES venue(venue_id)     ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT fk_vb_wedding FOREIGN KEY (wedding_id) REFERENCES wedding(wedding_id) ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT chk_vb_time   CHECK (end_time > start_time)
-);
-
--- ── Photographers ────────────────────────────────────────────
-CREATE TABLE photographer (
-    photographer_id SERIAL PRIMARY KEY,
-    name            VARCHAR(120) NOT NULL,
-    email           VARCHAR(120) NOT NULL UNIQUE,
-    phone_number    VARCHAR(30)  NOT NULL,
-    price_per_hour  NUMERIC(10,2) NOT NULL,
-    CONSTRAINT chk_ph_price CHECK (price_per_hour >= 0)
-);
-
--- ── Photographer Bookings ────────────────────────────────────
-CREATE TABLE photographer_booking (
-    booking_id      SERIAL PRIMARY KEY,
-    "date"          DATE        NOT NULL,
-    start_time      TIME        NOT NULL,
-    end_time        TIME        NOT NULL,
-    status          VARCHAR(30) NOT NULL,
-    photographer_id INTEGER     NOT NULL,
-    wedding_id      INTEGER     NOT NULL,
-    CONSTRAINT fk_pb_photographer FOREIGN KEY (photographer_id) REFERENCES photographer(photographer_id) ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT fk_pb_wedding      FOREIGN KEY (wedding_id)      REFERENCES wedding(wedding_id)           ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT chk_pb_time        CHECK (end_time > start_time)
-);
-
--- ── Bands ────────────────────────────────────────────────────
-CREATE TABLE band (
-    band_id        SERIAL PRIMARY KEY,
-    band_name      VARCHAR(120) NOT NULL,
-    genre          VARCHAR(60)  NOT NULL,
-    equipment      TEXT,
-    phone_number   VARCHAR(30)  NOT NULL,
-    price_per_hour NUMERIC(10,2) NOT NULL,
-    CONSTRAINT chk_band_price CHECK (price_per_hour >= 0)
-);
-
--- ── Band Bookings ────────────────────────────────────────────
-CREATE TABLE band_booking (
-    booking_id SERIAL PRIMARY KEY,
-    "date"     DATE        NOT NULL,
-    start_time TIME        NOT NULL,
-    end_time   TIME        NOT NULL,
-    status     VARCHAR(30) NOT NULL,
-    band_id    INTEGER     NOT NULL,
-    wedding_id INTEGER     NOT NULL,
-    CONSTRAINT fk_bb_band    FOREIGN KEY (band_id)    REFERENCES band(band_id)        ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT fk_bb_wedding FOREIGN KEY (wedding_id) REFERENCES wedding(wedding_id)  ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT chk_bb_time   CHECK (end_time > start_time)
-);
-
--- ── Registrars (NEW — was in HTML but missing from original schema) ──
-CREATE TABLE registrar (
-    registrar_id  SERIAL PRIMARY KEY,
-    name          VARCHAR(120) NOT NULL,
-    contact       VARCHAR(120) NOT NULL,
-    location      VARCHAR(150) NOT NULL,
-    working_hours VARCHAR(60)
-);
-
--- ── Registrar Bookings (NEW) ─────────────────────────────────
-CREATE TABLE registrar_booking (
-    booking_id    SERIAL PRIMARY KEY,
-    "date"        DATE        NOT NULL,
-    start_time    TIME        NOT NULL,
-    end_time      TIME        NOT NULL,
-    status        VARCHAR(30) NOT NULL,
-    registrar_id  INTEGER     NOT NULL,
-    wedding_id    INTEGER     NOT NULL,
-    CONSTRAINT fk_rb_registrar FOREIGN KEY (registrar_id) REFERENCES registrar(registrar_id) ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT fk_rb_wedding   FOREIGN KEY (wedding_id)   REFERENCES wedding(wedding_id)     ON UPDATE CASCADE ON DELETE CASCADE,
-    CONSTRAINT chk_rb_time     CHECK (end_time > start_time)
-);
-
--- ── Indexes ──────────────────────────────────────────────────
-CREATE INDEX idx_wedding_user   ON wedding(user_id);
-CREATE INDEX idx_event_wedding  ON event(wedding_id);
-CREATE INDEX idx_guest_wedding  ON guest(wedding_id);
-CREATE INDEX idx_vb_wedding     ON venue_booking(wedding_id);
-CREATE INDEX idx_vb_venue       ON venue_booking(venue_id);
-CREATE INDEX idx_pb_wedding     ON photographer_booking(wedding_id);
-CREATE INDEX idx_bb_wedding     ON band_booking(wedding_id);
-CREATE INDEX idx_rb_wedding     ON registrar_booking(wedding_id);
-CREATE INDEX idx_rsvp_event     ON event_rsvp(event_id);
-CREATE INDEX idx_att_event      ON attendance(event_id);
Index: rver.js
===================================================================
--- server.js	(revision 2421e105e89fe2b782209dc51528389c7da287f5)
+++ 	(revision )
@@ -1,1780 +1,0 @@
-﻿// =============================================================
-//  Wedding Planner — Complete Backend (Node.js + Express)
-//  Database: PostgreSQL via server
-//  Auth: Session-based (login / register)
-//  Email: Gmail via Nodemailer
-// =============================================================
-
-// Load environment variables
-require('dotenv').config();
-
-const express = require('express');
-const { Pool } = require('pg');
-const cors = require('cors');
-
-const app = express();
-
-app.use(cors({
-  origin: "http://localhost:3000",
-  credentials: true
-}));
-
-app.use(express.json());
-const bcrypt = require('bcryptjs');
-const session = require('express-session');
-const nodemailer = require('nodemailer');
-const crypto = require('crypto');
-const path = require('path');
-const PORT = process.env.PORT || 3000;
-
-// =============================================================
-//  MIDDLEWARE
-// =============================================================
-app.use(cors({ origin: 'http://localhost:3000', credentials: true }));
-app.use(express.json());
-app.use(express.urlencoded({ extended: true }));
-
-// Serve all HTML files from the same folder as this server.js
-app.use(express.static(path.join(__dirname)));
-
-// Session setup
-app.use(session({
-    secret: 'wedding_planner_secret_key_2025',
-    resave: false,
-    saveUninitialized: false,
-    cookie: {
-        secure: false,       // set true only if using HTTPS
-        httpOnly: true,
-        maxAge: 1000 * 60 * 60 * 8  // 8 hours
-    }
-}));
-
-// =============================================================
-//  DATABASE CONNECTION
-//
-// =============================================================
-const pool = new Pool({
-    user: process.env.DB_USER,
-    host: process.env.DB_HOST,
-    database: process.env.DB_NAME,
-    password: process.env.DB_PASSWORD,
-    port: parseInt(process.env.DB_PORT, 10),
-    ssl: { rejectUnauthorized: false }
-});
-
-// POST /api/weddings/:wid/guests/bulk — add multiple guests in a transaction
-app.post('/api/weddings/:wid/guests/bulk', requireAuth, async (req, res) => {
-    const { guests, sendEmails, bride_name, groom_name } = req.body;
-    if (!Array.isArray(guests) || guests.length === 0) return res.status(400).json({ error: 'guests array is required.' });
-
-    const wid = req.params.wid;
-    const client = await pool.connect();
-    try {
-        // Ensure wedding exists
-        const wRes = await client.query('SET search_path TO project; SELECT wedding_id, "date" FROM wedding WHERE wedding_id=$1', [wid]);
-        if (wRes.rows.length === 0) {
-            client.release();
-            return res.status(404).json({ error: 'Wedding not found.' });
-        }
-
-        // Fetch events for this wedding
-        const evRes = await client.query('SELECT event_id, event_type, "date", start_time, end_time FROM project.event WHERE wedding_id=$1 ORDER BY "date", start_time', [wid]);
-        const events = evRes.rows;
-
-        // Validate incoming data and ensure no duplicate guest names (both in payload and in DB)
-        const providedNames = [];
-        for (const g of guests) {
-            const first_name = (g.first_name || '').trim();
-            const last_name = (g.last_name || '').trim();
-            if (!first_name || !last_name) {
-                client.release();
-                return res.status(400).json({ error: 'Each guest must have first_name and last_name.' });
-            }
-            providedNames.push(`${first_name.toLowerCase()} ${last_name.toLowerCase()}`);
-        }
-
-        // Check duplicates inside the uploaded CSV/payload
-        const dupSet = providedNames.filter((v, i, a) => a.indexOf(v) !== i);
-        if (dupSet.length > 0) { client.release(); return res.status(400).json({ error: 'Duplicate guest names in upload are not allowed.' }); }
-
-        // Check duplicates already in DB for this wedding
-        if (providedNames.length > 0) {
-            // build parameter placeholders for names
-            const placeholders = providedNames.map((_, i) => `$${i + 2}`).join(',');
-            const params = [wid, ...providedNames];
-            const existing = await client.query(
-                `SELECT first_name, last_name FROM project.guest WHERE wedding_id=$1 AND lower(first_name || ' ' || last_name) IN (${placeholders})`,
-                params
-            );
-            if (existing.rows.length > 0) {
-                client.release();
-                return res.status(409).json({ error: 'One or more guests already exist for this wedding.' });
-            }
-        }
-
-        await client.query('BEGIN');
-        const inserted = [];
-        for (const g of guests) {
-            const first_name = (g.first_name || '').trim();
-            const last_name = (g.last_name || '').trim();
-            const email = (g.email || null);
-            const role = (g.role || null);
-            const ins = await client.query('INSERT INTO project.guest(first_name,last_name,email,wedding_id,role) VALUES ($1,$2,$3,$4,$5) RETURNING *', [first_name, last_name, email, wid, role]);
-            const guest = ins.rows[0];
-            inserted.push(guest);
-
-            // Create RSVP records for each event
-            for (const ev of events) {
-                try {
-                    await client.query(
-                        `INSERT INTO project.event_rsvp(status, response_date, guest_id, event_id)
-                         VALUES ('invited', CURRENT_DATE, $1, $2)
-                         ON CONFLICT (guest_id, event_id) DO NOTHING`,
-                        [guest.guest_id, ev.event_id]
-                    );
-                } catch (e) {
-                    // Log and continue
-                    console.warn('Could not create RSVP for guest', guest.guest_id, 'event', ev.event_id, e.message);
-                }
-            }
-        }
-
-        await client.query('COMMIT');
-
-        // Optionally send emails after commit — bride/groom names come from the frontend
-        // (entered by the user in Settings, stored locally in their browser only). The
-        // account owner's profile name is intentionally NOT used as a fallback anymore.
-        if (sendEmails) {
-            const bride = bride_name || 'the Bride';
-            const groom = groom_name || 'the Groom';
-            for (const guest of inserted) {
-                if (!guest.email) continue;
-                try {
-                    // Build event links
-                    const eventLinks = events.map(ev => {
-                        const token = crypto.createHmac('sha256', 'wedding_rsvp_secret').update(`${guest.guest_id}-${ev.event_id}`).digest('hex');
-                        const base = `http://localhost:${PORT}`;
-                        return `
-                            <tr>
-                              <td style="padding:8px 0; font-size:15px; color:#2c1f1f;">
-                                <strong>${ev.event_type}</strong> — ${ev.date} ${ev.start_time}–${ev.end_time}
-                              </td>
-                              <td style="padding:8px 0; text-align:right;">
-                                <a href="${base}/rsvp.html?guest=${guest.guest_id}&event=${ev.event_id}&token=${token}&action=accepted" style="background:#3a9e6b;color:#fff;padding:6px 14px;border-radius:6px;text-decoration:none;margin-right:6px;font-size:13px;">✅ Accept</a>
-                                <a href="${base}/rsvp.html?guest=${guest.guest_id}&event=${ev.event_id}&token=${token}&action=declined" style="background:#c94545;color:#fff;padding:6px 14px;border-radius:6px;text-decoration:none;font-size:13px;">❌ Decline</a>
-                              </td>
-                            </tr>`;
-                    }).join('');
-
-                    const mailOptions = {
-                        from: `"Wedding Planner" <${process.env.EMAIL_USER}>`,
-                        to: guest.email,
-                        subject: `You're Invited! 💍 ${bride} & ${groom}'s Wedding`,
-                        html: `
-                            <div style="font-family:'DM Sans',Arial,sans-serif;max-width:600px;margin:auto;background:#fdf8f3;border-radius:12px;overflow:hidden;border:1px solid #ecddd0;">
-                              <div style="background:linear-gradient(135deg,#d4606a,#e8949c);padding:32px;text-align:center;">
-                                <div style="font-size:36px;margin-bottom:8px;">💍</div>
-                                <h1 style="color:#fff;font-size:26px;margin:0;">You're Invited!</h1>
-                                <p style="color:rgba(255,255,255,0.9);margin:8px 0 0;">Wedding Invitation</p>
-                              </div>
-                              <div style="padding:32px;">
-                                <p style="font-size:16px;color:#2c1f1f;">Dear <strong>${guest.first_name} ${guest.last_name}</strong>,</p>
-                                <p style="font-size:15px;color:#6b4f4f;line-height:1.6;">Please RSVP for the events below:</p>
-                                <table style="width:100%;border-collapse:collapse;">${eventLinks}</table>
-                              </div>
-                              <div style="background:#f2e8db;padding:16px;text-align:center;"></div>
-                            </div>`
-                    };
-                    await transporter.sendMail(mailOptions);
-                } catch (mailErr) {
-                    console.warn('Failed to send invite to', guest.email, mailErr.message);
-                }
-            }
-        }
-
-        client.release();
-        res.json({ inserted: inserted.length, guests: inserted });
-    } catch (err) {
-        try { await client.query('ROLLBACK'); } catch (e) { /* ignore */ }
-        client.release();
-        console.error('Bulk guest insert error:', err.message);
-        if (err instanceof ApiError) return res.status(err.status).json({ error: err.message });
-        res.status(500).json({ error: 'Server error during bulk guest insert.' });
-    }
-});
-
-console.log('📡 Attempting to connect to PostgreSQL...');
-console.log(`   Host: ${process.env.DB_HOST}:${process.env.DB_PORT}`);
-console.log(`   Database: ${process.env.DB_NAME}`);
-
-pool.connect()
-    .then(client => {
-        console.log('✅  Connected to PostgreSQL database!');
-        client.release();
-    })
-    .catch(err => {
-        console.error('❌  DB connection error:', err.message);
-        console.error('   Make sure SSH tunnel is running on port 9999');
-        console.error('   SSH: ssh -L 9999:db_server:5432 t_wedding_planner2025@194.149.135.130');
-        console.error('   Full error:', err.stack);
-    });
-
-// Helper: always use the project schema
-const Q = (text, params) => pool.query(`SET search_path TO project; ${text}`, params);
-
-// =============================================================
-//  Service layer: Booking validation utilities
-// =============================================================
-class ApiError extends Error {
-    constructor(status, message) {
-        super(message);
-        this.status = status;
-    }
-}
-
-// Wrap pool.query to translate DB trigger exceptions into ApiError so endpoints return structured errors
-{
-    const origQuery = pool.query.bind(pool);
-    pool.query = async (text, params) => {
-        try {
-            return await origQuery(text, params);
-        } catch (err) {
-            // Postgres RAISE EXCEPTION yields SQLSTATE 'P0001' — translate to ApiError(409)
-            if (err && err.code === 'P0001') {
-                throw new ApiError(409, err.message);
-            }
-            throw err;
-        }
-    };
-}
-
-const BookingService = {
-    // Normalize date value (DATE from PG may be string or Date)
-    _fmtDate(d) {
-        if (!d) return null;
-        if (d instanceof Date) return d.toISOString().slice(0,10);
-        // assume string YYYY-MM-DD
-        return String(d).slice(0,10);
-    },
-
-    // Check if two time intervals overlap: [s1,e1) and [s2,e2)
-    isOverlapping(s1, e1, s2, e2) {
-        if (!s1 || !e1 || !s2 || !e2) return false;
-        // times are strings like HH:MM:SS or HH:MM
-        const toSec = t => {
-            const parts = String(t).split(':').map(Number);
-            return (parts[0]||0)*3600 + (parts[1]||0)*60 + (parts[2]||0);
-        };
-        const a1 = toSec(s1), b1 = toSec(e1), a2 = toSec(s2), b2 = toSec(e2);
-        return a1 < b2 && a2 < b1;
-    },
-
-    // Gather all bookings/events for a given wedding and date
-    async getBookingsForWeddingDate(wedding_id, date) {
-        const dateOnly = BookingService._fmtDate(date);
-        const q = `
-            SELECT 'venue' AS type, booking_id AS id, start_time, end_time
-            FROM project.venue_booking WHERE wedding_id=$1 AND "date" = $2::date
-            UNION ALL
-            SELECT 'church' AS type, booking_id AS id, start_time, end_time
-            FROM project.church_booking WHERE wedding_id=$1 AND "date" = $2::date
-            UNION ALL
-            SELECT 'registrar' AS type, booking_id AS id, start_time, end_time
-            FROM project.registrar_booking WHERE wedding_id=$1 AND "date" = $2::date
-            UNION ALL
-            SELECT 'photographer' AS type, booking_id AS id, start_time, end_time
-            FROM project.photographer_booking WHERE wedding_id=$1 AND "date" = $2::date
-            UNION ALL
-            SELECT 'band' AS type, booking_id AS id, start_time, end_time
-            FROM project.band_booking WHERE wedding_id=$1 AND "date" = $2::date
-            UNION ALL
-            SELECT 'event' AS type, event_id AS id, start_time, end_time
-            FROM project.event WHERE wedding_id=$1 AND "date" = $2::date
-        `;
-        const res = await pool.query(q, [wedding_id, dateOnly]);
-        return res.rows;
-    },
-
-    // Validate booking date equals wedding date
-    async validateDateMatchesWedding(wedding_id, date) {
-        // Use SQL date cast/comparison to avoid client-side format issues
-        const dateOnly = BookingService._fmtDate(date);
-        const r = await pool.query('SELECT 1 FROM project.wedding WHERE wedding_id=$1 AND "date" = $2::date', [wedding_id, dateOnly]);
-        if (r.rows.length === 0) {
-            // Determine whether wedding not found or date mismatch
-            const exists = await pool.query('SELECT 1 FROM project.wedding WHERE wedding_id=$1', [wedding_id]);
-            if (exists.rows.length === 0) throw new ApiError(404, 'Wedding not found.');
-            throw new ApiError(400, 'Booking is only allowed on the wedding date.');
-        }
-    },
-
-    // Validate time slot overlap across all bookings for the wedding/date
-    // exclude optional { type, id } to allow updating existing record
-    async validateNoOverlap(wedding_id, date, start_time, end_time, exclude) {
-        const dateOnly = BookingService._fmtDate(date);
-        const bookings = await BookingService.getBookingsForWeddingDate(wedding_id, dateOnly);
-        for (const b of bookings) {
-            if (exclude && String(exclude.type) === String(b.type) && String(exclude.id) === String(b.id)) continue;
-            if (BookingService.isOverlapping(start_time, end_time, b.start_time, b.end_time)) {
-                throw new ApiError(409, 'This has been already booked, please change date or time to rebook');
-            }
-        }
-    },
-
-    // Registrar location must match wedding venue location for that wedding date
-    // We require an existing venue booking on the wedding date and compare locations
-    async validateRegistrarLocation(wedding_id, date, registrar_id) {
-        // find registrar
-        const r = await pool.query('SELECT location FROM project.registrar WHERE registrar_id=$1', [registrar_id]);
-        if (r.rows.length === 0) throw new ApiError(404, 'Registrar not found.');
-        const regLoc = r.rows[0].location;
-
-        // find venue booking(s) for the wedding on the date
-        const dateOnly = BookingService._fmtDate(date);
-        const vb = await pool.query('SELECT v.location FROM project.venue_booking vb JOIN project.venue v ON vb.venue_id=v.venue_id WHERE vb.wedding_id=$1 AND vb."date" = $2::date', [wedding_id, dateOnly]);
-        if (vb.rows.length === 0) {
-            // no venue booked on that date -> not allowed
-            throw new ApiError(400, 'Registrar booking is only allowed at the wedding venue location.');
-        }
-        // require at least one matching venue location
-        const match = vb.rows.some(x => String(x.location).trim().toLowerCase() === String(regLoc).trim().toLowerCase());
-        if (!match) throw new ApiError(400, 'Registrar booking is only allowed at the wedding venue location.');
-    }
-    ,
-
-    // Validate a resource (venue/church/registrar/band/photographer) is not already booked
-    // exclude: optional { table: 'venue_booking'|'church_booking'|..., id: booking_id }
-    async validateResourceAvailability(type, resourceId, date, start_time, end_time, exclude) {
-        const mapping = {
-            venue: { table: 'project.venue_booking', col: 'venue_id', idCol: 'booking_id' },
-            church: { table: 'project.church_booking', col: 'church_id', idCol: 'booking_id' },
-            registrar: { table: 'project.registrar_booking', col: 'registrar_id', idCol: 'booking_id' },
-            band: { table: 'project.band_booking', col: 'band_id', idCol: 'booking_id' },
-            photographer: { table: 'project.photographer_booking', col: 'photographer_id', idCol: 'booking_id' }
-        };
-        const m = mapping[type];
-        if (!m) return; // nothing to validate
-        // Build query to find overlapping bookings for the same resource regardless of wedding
-        let q = `SELECT ${m.idCol} FROM ${m.table} WHERE ${m.col}=$1 AND "date" = $2::date AND NOT (end_time <= $3 OR start_time >= $4)`;
-        const params = [resourceId, date, start_time, end_time];
-        if (exclude && exclude.id) { q += ` AND ${m.idCol} != $5`; params.push(exclude.id); }
-        const r = await pool.query(q, params);
-        if (r.rows.length > 0) throw new ApiError(409, 'This has been already booked, please change date or time to rebook');
-    }
-};
-
-const BudgetService = {
-    bookingHours(startTime, endTime) {
-        const toSec = t => {
-            const parts = String(t).split(':').map(Number);
-            return (parts[0] || 0) * 3600 + (parts[1] || 0) * 60 + (parts[2] || 0);
-        };
-        return Math.max(0, (toSec(endTime) - toSec(startTime)) / 3600);
-    },
-
-    async calculateSummary(weddingId) {
-        // Venue cost is calculated per confirmed venue booking as price_per_guest * number of accepted guests
-        const venueRes = await pool.query(
-            `SELECT COALESCE(SUM(v.price_per_guest * COALESCE(gc.confirmed_count,0)), 0) AS total
-             FROM project.venue_booking vb
-             JOIN project.venue v ON vb.venue_id = v.venue_id
-             LEFT JOIN (
-                 SELECT ev."date" AS ev_date, COUNT(DISTINCT er.guest_id) AS confirmed_count
-                 FROM project.event_rsvp er
-                 JOIN project.event ev ON er.event_id = ev.event_id
-                 WHERE er.status = 'accepted' AND ev.wedding_id = $1
-                 GROUP BY ev."date"
-             ) gc ON gc.ev_date = vb."date"
-             WHERE vb.wedding_id = $1 AND vb.status = 'confirmed'`,
-            [weddingId]
-        );
-
-        const photoRes = await pool.query(
-            `SELECT COALESCE(SUM(
-                EXTRACT(EPOCH FROM (pb.end_time - pb.start_time)) / 3600 * p.price_per_hour
-             ), 0) AS total
-             FROM project.photographer_booking pb
-             JOIN project.photographer p ON pb.photographer_id = p.photographer_id
-             WHERE pb.wedding_id = $1 AND pb.status = 'confirmed'`,
-            [weddingId]
-        );
-
-        const bandRes = await pool.query(
-            `SELECT COALESCE(SUM(
-                EXTRACT(EPOCH FROM (bb.end_time - bb.start_time)) / 3600 * b.price_per_hour
-             ), 0) AS total
-             FROM project.band_booking bb
-             JOIN project.band b ON bb.band_id = b.band_id
-             WHERE bb.wedding_id = $1 AND bb.status = 'confirmed'`,
-            [weddingId]
-        );
-
-        const venueCost = Number(venueRes.rows[0].total || 0);
-        const photographerCost = Number(photoRes.rows[0].total || 0);
-        const bandCost = Number(bandRes.rows[0].total || 0);
-        const totalCost = venueCost + photographerCost + bandCost;
-
-        return {
-            wedding_id: Number(weddingId),
-            venue_cost: venueCost,
-            photographer_cost: photographerCost,
-            band_cost: bandCost,
-            total_cost: totalCost
-        };
-    },
-
-    async syncWeddingBudget(weddingId) {
-        // Only recalculate — do NOT overwrite the user's predefined budget column
-        return await BudgetService.calculateSummary(weddingId);
-    },
-
-    // Returns predefined budget vs actual spending for a given wedding
-    async getBudgetStatus(weddingId) {
-        const [summary, wRow] = await Promise.all([
-            BudgetService.calculateSummary(weddingId),
-            pool.query('SELECT budget FROM project.wedding WHERE wedding_id=$1', [weddingId])
-        ]);
-        const predefined = Number((wRow.rows[0] || {}).budget || 0);
-        const spent      = summary.total_cost;
-        const exceeded   = predefined > 0 && spent > predefined;
-        return {
-            predefined_budget: predefined,
-            total_cost: spent,
-            exceeded,
-            over_by: exceeded ? +(spent - predefined).toFixed(2) : 0,
-            venue_cost: summary.venue_cost,
-            photographer_cost: summary.photographer_cost,
-            band_cost: summary.band_cost
-        };
-    }
-};
-
-// =============================================================
-//  EMAIL TRANSPORTER
-// =============================================================
-const transporter = nodemailer.createTransport({
-    service: 'gmail',
-    auth: {
-        user: process.env.EMAIL_USER,
-        pass: process.env.EMAIL_PASS
-    }
-});
-
-// =============================================================
-//  AUTH MIDDLEWARE — protect routes that require login
-// =============================================================
-function requireAuth(req, res, next) {
-    if (req.session && req.session.user) return next();
-    return res.status(401).json({ error: 'Not authenticated. Please log in.' });
-}
-
-// =============================================================
-//  AUTH ROUTES
-// =============================================================
-
-// POST /api/auth/register
-app.post('/api/auth/register', async (req, res) => {
-    const { first_name, last_name, email, password, phone_number, gender, birthday } = req.body;
-    if (!first_name || !last_name || !email || !password)
-        return res.status(400).json({ error: 'First name, last name, email and password are required.' });
-
-    try {
-        // Check email not already taken
-        const exists = await pool.query(
-            'SELECT user_id FROM project."user" WHERE email = $1', [email]
-        );
-        if (exists.rows.length > 0)
-            return res.status(409).json({ error: 'Email already registered.' });
-
-        const hash = await bcrypt.hash(password, 10);
-
-        const result = await pool.query(
-            `INSERT INTO project."user"(first_name, last_name, email, password_hash, phone_number, gender, birthday)
-             VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING user_id, first_name, last_name, email, gender`,
-            [first_name, last_name, email, hash, phone_number || null, gender || null, birthday || null]
-        );
-
-        const user = result.rows[0];
-        req.session.user = user;
-        res.status(201).json({ message: 'Registered successfully.', user });
-    } catch (err) {
-        console.error('Register error:', err.message);
-        res.status(500).json({ error: 'Server error during registration.' });
-    }
-});
-
-// POST /api/auth/login
-app.post('/api/auth/login', async (req, res) => {
-    const { email, password } = req.body;
-    if (!email || !password)
-        return res.status(400).json({ error: 'Email and password are required.' });
-
-    try {
-        const result = await pool.query(
-            'SELECT * FROM project."user" WHERE email = $1', [email]
-        );
-        if (result.rows.length === 0)
-            return res.status(401).json({ error: 'Invalid email or password.' });
-
-        const user = result.rows[0];
-        const match = await bcrypt.compare(password, user.password_hash);
-        if (!match)
-            return res.status(401).json({ error: 'Invalid email or password.' });
-
-        req.session.user = {
-            user_id: user.user_id,
-            first_name: user.first_name,
-            last_name: user.last_name,
-            email: user.email,
-            gender: user.gender
-        };
-        res.json({ message: 'Logged in.', user: req.session.user });
-    } catch (err) {
-        console.error('Login error:', err.message);
-        res.status(500).json({ error: 'Server error during login.' });
-    }
-});
-
-// POST /api/auth/logout
-app.post('/api/auth/logout', (req, res) => {
-    req.session.destroy(() => res.json({ message: 'Logged out.' }));
-});
-
-// GET /api/auth/me — check current session
-app.get('/api/auth/me', (req, res) => {
-    if (req.session && req.session.user)
-        return res.json({ user: req.session.user });
-    res.status(401).json({ error: 'Not logged in.' });
-});
-
-// =============================================================
-//  USER / PROFILE
-// =============================================================
-
-// GET /api/users/:id
-app.get('/api/users/:id', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            'SELECT user_id, first_name, last_name, email, phone_number, gender, birthday FROM project."user" WHERE user_id = $1',
-            [req.params.id]
-        );
-        if (result.rows.length === 0) return res.status(404).json({ error: 'User not found.' });
-        res.json(result.rows[0]);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// PUT /api/users/:id — update profile
-app.put('/api/users/:id', requireAuth, async (req, res) => {
-    const { first_name, last_name, email, phone_number, gender, birthday } = req.body;
-    try {
-        const result = await pool.query(
-            `UPDATE project."user"
-             SET first_name=$1, last_name=$2, email=$3, phone_number=$4, gender=$5, birthday=$6
-             WHERE user_id=$7 RETURNING user_id, first_name, last_name, email, phone_number, gender, birthday`,
-            [first_name, last_name, email, phone_number, gender, birthday || null, req.params.id]
-        );
-        if (result.rows.length === 0) return res.status(404).json({ error: 'User not found.' });
-        // Update session too
-        req.session.user = { ...req.session.user, ...result.rows[0] };
-        res.json(result.rows[0]);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// =============================================================
-//  WEDDINGS  (UC0003)
-// =============================================================
-
-// GET /api/weddings — get wedding(s) for logged-in user
-app.get('/api/weddings', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            'SELECT * FROM project.wedding WHERE user_id = $1 ORDER BY date',
-            [req.session.user.user_id]
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// GET /api/weddings/:id
-app.get('/api/weddings/:id', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            'SELECT * FROM project.wedding WHERE wedding_id = $1 AND user_id = $2',
-            [req.params.id, req.session.user.user_id]
-        );
-        if (result.rows.length === 0) return res.status(404).json({ error: 'Wedding not found.' });
-        res.json(result.rows[0]);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// POST /api/weddings — create wedding
-app.post('/api/weddings', requireAuth, async (req, res) => {
-    const { date, budget, notes } = req.body;
-    if (!date) return res.status(400).json({ error: 'Wedding date is required.' });
-    try {
-        const result = await pool.query(
-            'INSERT INTO project.wedding("date", budget, notes, user_id) VALUES ($1,$2,$3,$4) RETURNING *',
-            [date, budget ? Number(budget) : 0, notes || null, req.session.user.user_id]
-        );
-        res.status(201).json(result.rows[0]);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// PUT /api/weddings/:id
-app.put('/api/weddings/:id', requireAuth, async (req, res) => {
-    const { date, budget, notes } = req.body;
-    try {
-        const result = await pool.query(
-            `UPDATE project.wedding SET "date"=$1, budget=$2, notes=$3
-             WHERE wedding_id=$4 AND user_id=$5 RETURNING *`,
-            [date, budget !== undefined ? Number(budget) : null, notes || null, req.params.id, req.session.user.user_id]
-        );
-        if (result.rows.length === 0) return res.status(404).json({ error: 'Wedding not found.' });
-        res.json(result.rows[0]);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// GET /api/weddings/:id/budget — calculated from confirmed bookings vs predefined budget
-app.get('/api/weddings/:id/budget', requireAuth, async (req, res) => {
-    try {
-        const wedding = await pool.query(
-            'SELECT wedding_id FROM project.wedding WHERE wedding_id = $1 AND user_id = $2',
-            [req.params.id, req.session.user.user_id]
-        );
-        if (wedding.rows.length === 0) return res.status(404).json({ error: 'Wedding not found.' });
-        const status = await BudgetService.getBudgetStatus(req.params.id);
-        res.json(status);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// DELETE /api/weddings/:id
-app.delete('/api/weddings/:id', requireAuth, async (req, res) => {
-    try {
-        await pool.query(
-            'DELETE FROM project.wedding WHERE wedding_id=$1 AND user_id=$2',
-            [req.params.id, req.session.user.user_id]
-        );
-        res.json({ message: 'Wedding deleted.' });
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// =============================================================
-//  EVENTS  (UC0005)
-// =============================================================
-
-// GET /api/weddings/:wid/events
-app.get('/api/weddings/:wid/events', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            'SELECT * FROM project.event WHERE wedding_id=$1 ORDER BY "date", start_time',
-            [req.params.wid]
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// POST /api/weddings/:wid/events
-app.post('/api/weddings/:wid/events', requireAuth, async (req, res) => {
-    const { event_type, date, start_time, end_time, status } = req.body;
-    if (!event_type || !date || !start_time || !end_time)
-        return res.status(400).json({ error: 'event_type, date, start_time, end_time are required.' });
-    try {
-        // Business validations
-        await BookingService.validateDateMatchesWedding(req.params.wid, date);
-        await BookingService.validateNoOverlap(req.params.wid, date, start_time, end_time, null);
-
-        const result = await pool.query(
-            `INSERT INTO project.event(event_type,"date",start_time,end_time,status,wedding_id)
-             VALUES ($1,$2,$3,$4,$5,$6) RETURNING *`,
-            [event_type, date, start_time, end_time, status || 'scheduled', req.params.wid]
-        );
-        res.status(201).json(result.rows[0]);
-    } catch (err) {
-        console.error(err.message);
-        if (err instanceof ApiError) return res.status(err.status).json({ error: err.message });
-        // Postgres RAISE EXCEPTION uses SQLSTATE 'P0001' - surface message as conflict for the client
-        if (err && (err.code === 'P0001' || String(err.message).toLowerCase().includes('already booked') || String(err.message).toLowerCase().includes('conflict'))) {
-            return res.status(409).json({ error: err.message });
-        }
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// PUT /api/events/:id
-app.put('/api/events/:id', requireAuth, async (req, res) => {
-    const { event_type, date, start_time, end_time, status } = req.body;
-    try {
-        // fetch existing event to know wedding_id and current values
-        const evRes = await pool.query('SELECT * FROM project.event WHERE event_id=$1', [req.params.id]);
-        if (evRes.rows.length === 0) return res.status(404).json({ error: 'Event not found.' });
-        const ev = evRes.rows[0];
-        const weddingId = ev.wedding_id;
-        // Validate date matches wedding and no overlap (exclude this event)
-        await BookingService.validateDateMatchesWedding(weddingId, date);
-        await BookingService.validateNoOverlap(weddingId, date, start_time, end_time, { type: 'event', id: req.params.id });
-
-        const result = await pool.query(
-            `UPDATE project.event SET event_type=$1,"date"=$2,start_time=$3,end_time=$4,status=$5
-             WHERE event_id=$6 RETURNING *`,
-            [event_type, date, start_time, end_time, status, req.params.id]
-        );
-        if (result.rows.length === 0) return res.status(404).json({ error: 'Event not found.' });
-        res.json(result.rows[0]);
-    } catch (err) {
-        console.error(err.message);
-        if (err instanceof ApiError) return res.status(err.status).json({ error: err.message });
-        if (err && (err.code === 'P0001' || String(err.message).toLowerCase().includes('already booked') || String(err.message).toLowerCase().includes('conflict'))) {
-            return res.status(409).json({ error: err.message });
-        }
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// DELETE /api/events/:id
-app.delete('/api/events/:id', requireAuth, async (req, res) => {
-    try {
-        await pool.query('DELETE FROM project.event WHERE event_id=$1', [req.params.id]);
-        res.json({ message: 'Event deleted.' });
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// =============================================================
-//  GUESTS  (UC0004)
-// =============================================================
-
-// GET /api/weddings/:wid/guests
-app.get('/api/weddings/:wid/guests', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            'SELECT guest_id, first_name, last_name, email, role, wedding_id FROM project.guest WHERE wedding_id=$1 ORDER BY last_name, first_name',
-            [req.params.wid]
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// POST /api/weddings/:wid/guests — add guest + send email invite
-app.post('/api/weddings/:wid/guests', requireAuth, async (req, res) => {
-    const { first_name, last_name, email, role, bride_name, groom_name } = req.body;
-    if (!first_name || !last_name)
-        return res.status(400).json({ error: 'First name and last name are required.' });
-
-    try {
-        // Prevent duplicate guest name for the same wedding
-        const dup = await pool.query(
-            `SELECT guest_id FROM project.guest WHERE wedding_id=$1 AND lower(first_name || ' ' || last_name) = lower($2 || ' ' || $3)`,
-            [req.params.wid, first_name, last_name]
-        );
-        if (dup.rows.length > 0) return res.status(409).json({ error: 'Guest with the same name already exists for this wedding.' });
-
-        // Insert guest
-        const result = await pool.query(
-            'INSERT INTO project.guest(first_name,last_name,email,wedding_id) VALUES ($1,$2,$3,$4) RETURNING *',
-            [first_name, last_name, email || null, req.params.wid]
-        );
-        const guest = result.rows[0];
-
-        // Fetch wedding info for the email (use account owner name as the couple)
-        const weddingResult = await pool.query(
-            `SELECT w."date", u.first_name AS owner_first, u.last_name AS owner_last
-             FROM project.wedding w
-             JOIN project."user" u ON w.user_id = u.user_id
-             WHERE w.wedding_id = $1`,
-            [req.params.wid]
-        );
-        const wedding = weddingResult.rows[0] || {};
-
-        // Fetch all events for this wedding so guest can RSVP
-        const eventsResult = await pool.query(
-            'SELECT * FROM project.event WHERE wedding_id=$1 ORDER BY "date", start_time',
-            [req.params.wid]
-        );
-        const events = eventsResult.rows;
-
-        // Create initial RSVP records with status "invited" for each event
-        for (const event of events) {
-            try {
-                await pool.query(
-                    `INSERT INTO project.event_rsvp(status, response_date, guest_id, event_id)
-                     VALUES ('invited', CURRENT_DATE, $1, $2)
-                     ON CONFLICT (guest_id, event_id) DO NOTHING`,
-                    [guest.guest_id, event.event_id]
-                );
-            } catch (rsvpErr) {
-                console.warn(`⚠️  Could not create RSVP record for guest ${guest.guest_id}, event ${event.event_id}:`, rsvpErr.message);
-            }
-        }
-
-        // Send email invitation if email provided
-        if (email && events.length > 0) {
-            // Generate a secure token per event for the RSVP links
-            const eventLinks = events.map(ev => {
-                const token = crypto
-                    .createHmac('sha256', 'wedding_rsvp_secret')
-                    .update(`${guest.guest_id}-${ev.event_id}`)
-                    .digest('hex');
-                const base = `http://localhost:${PORT}`;
-                return `
-                    <tr>
-                      <td style="padding:8px 0; font-size:15px; color:#2c1f1f;">
-                        <strong>${ev.event_type}</strong> — ${ev.date} ${ev.start_time}–${ev.end_time}
-                      </td>
-                      <td style="padding:8px 0; text-align:right;">
-                        <a href="${base}/rsvp.html?guest=${guest.guest_id}&event=${ev.event_id}&token=${token}&action=accepted"
-                           style="background:#3a9e6b;color:#fff;padding:6px 14px;border-radius:6px;text-decoration:none;margin-right:6px;font-size:13px;">
-                          ✅ Accept
-                        </a>
-                        <a href="${base}/rsvp.html?guest=${guest.guest_id}&event=${ev.event_id}&token=${token}&action=declined"
-                           style="background:#c94545;color:#fff;padding:6px 14px;border-radius:6px;text-decoration:none;font-size:13px;">
-                          ❌ Decline
-                        </a>
-                      </td>
-                    </tr>`;
-            }).join('');
-
-            // Bride/groom names are entered by the user in Settings (stored locally in their
-            // browser only) and passed in from the frontend per-request. The account owner's
-            // profile name is intentionally NOT used here anymore.
-            const bride = bride_name || 'the Bride';
-            const groom = groom_name || 'the Groom';
-
-            const mailOptions = {
-                from: `"Wedding Planner" <${process.env.EMAIL_USER}>`,
-                to: email,
-                subject: `You're Invited! 💍 ${bride} & ${groom}'s Wedding`,
-                html: `
-                    <div style="font-family:'DM Sans',Arial,sans-serif;max-width:600px;margin:auto;background:#fdf8f3;border-radius:12px;overflow:hidden;border:1px solid #ecddd0;">
-                      <div style="background:linear-gradient(135deg,#d4606a,#e8949c);padding:32px;text-align:center;">
-                        <div style="font-size:36px;margin-bottom:8px;">💍</div>
-                        <h1 style="color:#fff;font-size:26px;margin:0;">You're Invited!</h1>
-                        <p style="color:rgba(255,255,255,0.9);margin:8px 0 0;">Wedding of ${bride} &amp; ${groom}</p>
-                      </div>
-                      <div style="padding:32px;">
-                        <p style="font-size:16px;color:#2c1f1f;">Dear <strong>${first_name} ${last_name}</strong>,</p>
-                        <p style="font-size:15px;color:#6b4f4f;line-height:1.6;">
-                          We are delighted to invite you to celebrate the wedding of
-                          <strong>${bride} &amp; ${groom}</strong>
-                          on <strong>${wedding.date}</strong>.
-                        </p>
-                        <p style="font-size:15px;color:#2c1f1f;font-weight:600;margin-top:24px;">Please RSVP for each event:</p>
-                        <table style="width:100%;border-collapse:collapse;">
-                          ${eventLinks}
-                        </table>
-                        <p style="font-size:13px;color:#a08080;margin-top:24px;">
-                          We look forward to celebrating with you. If you have any questions, please contact us directly.
-                        </p>
-                      </div>
-                      <div style="background:#f2e8db;padding:16px;text-align:center;">
-                        <p style="font-size:12px;color:#a08080;margin:0;">Wedding Planner App · Sent with love 💌</p>
-                      </div>
-                    </div>`
-            };
-
-            try {
-                await transporter.sendMail(mailOptions);
-                console.log(`📧  Invite sent to ${email}`);
-            } catch (mailErr) {
-                // Don't fail the guest insert if email fails — just log it
-                console.warn('⚠️  Email send failed (guest still added):', mailErr.message);
-            }
-        }
-
-        res.status(201).json({ guest, emailSent: !!(email && events.length > 0) });
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// PUT /api/guests/:id
-app.put('/api/guests/:id', requireAuth, async (req, res) => {
-    const { first_name, last_name, email, role } = req.body;
-    try {
-        const result = await pool.query(
-            'UPDATE project.guest SET first_name=$1,last_name=$2,email=$3,role=$4 WHERE guest_id=$5 RETURNING *',
-            [first_name, last_name, email || null, role || 'Guest', req.params.id]
-        );
-        if (result.rows.length === 0) return res.status(404).json({ error: 'Guest not found.' });
-        res.json(result.rows[0]);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// DELETE /api/guests/:id
-app.delete('/api/guests/:id', requireAuth, async (req, res) => {
-    try {
-        await pool.query('DELETE FROM project.guest WHERE guest_id=$1', [req.params.id]);
-        res.json({ message: 'Guest deleted.' });
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// =============================================================
-//  RSVP  (UC0009)
-// =============================================================
-
-// GET /api/rsvp?guest_id=&event_id= — check existing RSVP
-app.get('/api/rsvp', async (req, res) => {
-    const { guest_id, event_id } = req.query;
-    try {
-        const result = await pool.query(
-            'SELECT * FROM project.event_rsvp WHERE guest_id=$1 AND event_id=$2',
-            [guest_id, event_id]
-        );
-        res.json(result.rows[0] || null);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// POST /api/rsvp — guest submits RSVP (called from rsvp.html, no auth needed)
-app.post('/api/rsvp', async (req, res) => {
-    const { guest_id, event_id, status, token } = req.body;
-    if (!guest_id || !event_id || !status)
-        return res.status(400).json({ error: 'guest_id, event_id and status are required.' });
-
-    // Verify token (protects against random submissions)
-    const expected = crypto
-        .createHmac('sha256', 'wedding_rsvp_secret')
-        .update(`${guest_id}-${event_id}`)
-        .digest('hex');
-    if (token !== expected)
-        return res.status(403).json({ error: 'Invalid or expired RSVP link.' });
-
-    const validStatuses = ['accepted', 'declined', 'pending', 'invited'];
-    if (!validStatuses.includes(status))
-        return res.status(400).json({ error: 'Status must be accepted, declined, pending, or invited.' });
-
-    try {
-        // Upsert: update if exists, insert if not
-        const result = await pool.query(
-            `INSERT INTO project.event_rsvp(status, response_date, guest_id, event_id)
-             VALUES ($1, CURRENT_DATE, $2, $3)
-             ON CONFLICT (guest_id, event_id)
-             DO UPDATE SET status=$1, response_date=CURRENT_DATE
-             RETURNING *`,
-            [status, guest_id, event_id]
-        );
-
-        // Fetch guest + event info for the confirmation response
-        const guestInfo = await pool.query(
-            `SELECT g.first_name, g.last_name, e.event_type, e.date, e.start_time, e.end_time
-             FROM project.guest g, project.event e
-             WHERE g.guest_id=$1 AND e.event_id=$2`,
-            [guest_id, event_id]
-        );
-
-        // Sync budget when RSVP affects attendee counts
-        const info = guestInfo.rows[0] || null;
-        if (info && (status === 'accepted' || status === 'declined')) {
-            try {
-                // e.wedding_id is not selected above — fetch the event to get wedding_id
-                const ev = await pool.query('SELECT wedding_id FROM project.event WHERE event_id=$1', [event_id]);
-                if (ev.rows.length > 0) await BudgetService.syncWeddingBudget(ev.rows[0].wedding_id);
-            } catch (e) {
-                console.warn('Budget sync after RSVP failed:', e.message);
-            }
-        }
-
-        // Ensure there is an attendance record for this guest/event so they appear in Seating (unassigned table)
-        try {
-            // Map RSVP status to attendance status used in seating
-            let attendanceStatus = 'pending';
-            if (status === 'accepted') attendanceStatus = 'attending';
-            else if (status === 'declined') attendanceStatus = 'not_attending';
-
-            // Fetch guest role to store in attendance (fallback to 'Guest')
-            const guestRoleRes = await pool.query('SELECT role FROM project.guest WHERE guest_id=$1', [guest_id]);
-            const guestRole = guestRoleRes.rows[0]?.role || 'Guest';
-
-            // Upsert attendance but DO NOT overwrite table_number if already assigned
-            await pool.query(
-                `INSERT INTO project.attendance(status, table_number, role, guest_id, event_id)
-                 VALUES ($1, NULL, $2, $3, $4)
-                 ON CONFLICT (guest_id, event_id)
-                 DO UPDATE SET status = $1, role = $2
-                 `,
-                [attendanceStatus, guestRole, guest_id, event_id]
-            );
-
-            // If attendee accepted, ensure budget sync
-            if (attendanceStatus === 'attending') {
-                try {
-                    const ev = await pool.query('SELECT wedding_id FROM project.event WHERE event_id=$1', [event_id]);
-                    if (ev.rows.length > 0) await BudgetService.syncWeddingBudget(ev.rows[0].wedding_id);
-                } catch (e) { console.warn('Budget sync after attendance upsert failed:', e.message); }
-            }
-        } catch (e) {
-            console.warn('Could not ensure attendance record after RSVP:', e.message);
-        }
-
-        res.json({
-            rsvp: result.rows[0],
-            guest: info
-        });
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// GET /api/weddings/:wid/rsvp — all RSVPs for a wedding (for admin view)
-app.get('/api/weddings/:wid/rsvp', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            `SELECT er.response_id, er.status, er.response_date,
-                    g.guest_id, g.first_name, g.last_name, g.email,
-                    e.event_id, e.event_type, e.date AS event_date
-             FROM project.event_rsvp er
-             JOIN project.guest g ON er.guest_id = g.guest_id
-             JOIN project.event e ON er.event_id = e.event_id
-             WHERE e.wedding_id = $1
-             ORDER BY e.date, g.last_name`,
-            [req.params.wid]
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// =============================================================
-//  ATTENDANCE / SEATING  (UC0011)
-// =============================================================
-
-// GET /api/weddings/:wid/attendance — full seating list for a wedding
-app.get('/api/weddings/:wid/attendance', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            `SELECT a.attendance_id, a.status, a.table_number, a.role,
-                    g.guest_id, g.first_name, g.last_name,
-                    e.event_id, e.event_type
-             FROM project.attendance a
-             JOIN project.guest g ON a.guest_id = g.guest_id
-             JOIN project.event e ON a.event_id = e.event_id
-             WHERE e.wedding_id = $1
-             ORDER BY a.table_number NULLS LAST, g.last_name`,
-            [req.params.wid]
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// GET /api/events/:eid/attendance — seating for a specific event
-app.get('/api/events/:eid/attendance', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            `SELECT a.attendance_id, a.status, a.table_number, a.role,
-                    g.guest_id, g.first_name, g.last_name
-             FROM project.attendance a
-             JOIN project.guest g ON a.guest_id = g.guest_id
-             WHERE a.event_id = $1
-             ORDER BY a.table_number NULLS LAST, g.last_name`,
-            [req.params.eid]
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// POST /api/attendance — assign guest to event with table + role
-app.post('/api/attendance', requireAuth, async (req, res) => {
-    const { table_number, role, guest_id, event_id } = req.body;
-    if (!guest_id || !event_id || !role)
-        return res.status(400).json({ error: 'guest_id, event_id and role are required.' });
-    try {
-        // Fetch the guest's RSVP status for this event
-        const rsvpResult = await pool.query(
-            'SELECT status FROM project.event_rsvp WHERE guest_id=$1 AND event_id=$2',
-            [guest_id, event_id]
-        );
-        const rsvpStatus = rsvpResult.rows[0]?.status || 'pending';
-
-        // Enforce table capacity limit (max 10 per table)
-        if (table_number) {
-            const cntRes = await pool.query(
-                `SELECT COUNT(*) AS cnt FROM project.attendance WHERE event_id=$1 AND table_number=$2`,
-                [event_id, table_number]
-            );
-            const cnt = Number(cntRes.rows[0].cnt || 0);
-            if (cnt >= 10) return res.status(400).json({ error: 'Table capacity exceeded (max 10 guests per table).' });
-        }
-
-        const result = await pool.query(
-            `INSERT INTO project.attendance(status, table_number, role, guest_id, event_id)
-             VALUES ($1,$2,$3,$4,$5)
-             ON CONFLICT (guest_id, event_id)
-             DO UPDATE SET status=$1, table_number=$2, role=$3
-             RETURNING *`,
-            [rsvpStatus, table_number || null, role, guest_id, event_id]
-        );
-
-        // Sync budget if this guest is accepted (affects venue per-guest cost)
-        try {
-            if (rsvpStatus === 'accepted') {
-                const ev = await pool.query('SELECT wedding_id FROM project.event WHERE event_id=$1', [event_id]);
-                if (ev.rows.length > 0) await BudgetService.syncWeddingBudget(ev.rows[0].wedding_id);
-            }
-        } catch (e) { console.warn('Budget sync after attendance change failed:', e.message); }
-
-        res.status(201).json(result.rows[0]);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// PUT /api/attendance/:id — update seating assignment
-app.put('/api/attendance/:id', requireAuth, async (req, res) => {
-    const { table_number, role } = req.body;
-    try {
-        // Fetch the attendance record to get guest_id and event_id
-        const attendanceRecord = await pool.query(
-            'SELECT guest_id, event_id FROM project.attendance WHERE attendance_id=$1',
-            [req.params.id]
-        );
-        if (attendanceRecord.rows.length === 0) 
-            return res.status(404).json({ error: 'Record not found.' });
-        
-        const { guest_id, event_id } = attendanceRecord.rows[0];
-
-        // Fetch the guest's RSVP status for this event
-        const rsvpResult = await pool.query(
-            'SELECT status FROM project.event_rsvp WHERE guest_id=$1 AND event_id=$2',
-            [guest_id, event_id]
-        );
-        const rsvpStatus = rsvpResult.rows[0]?.status || 'pending';
-
-        // Enforce table capacity (exclude current attendance record from the count)
-        if (table_number) {
-            const cntRes = await pool.query(
-                `SELECT COUNT(*) AS cnt FROM project.attendance WHERE event_id=$1 AND table_number=$2 AND attendance_id != $3`,
-                [event_id, table_number, req.params.id]
-            );
-            const cnt = Number(cntRes.rows[0].cnt || 0);
-            if (cnt >= 10) return res.status(400).json({ error: 'Table capacity exceeded (max 10 guests per table).' });
-        }
-
-        const result = await pool.query(
-            `UPDATE project.attendance SET status=$1, table_number=$2, role=$3
-             WHERE attendance_id=$4 RETURNING *`,
-            [rsvpStatus, table_number || null, role, req.params.id]
-        );
-        if (result.rows.length === 0) return res.status(404).json({ error: 'Record not found.' });
-
-        try {
-            if (rsvpStatus === 'accepted') {
-                const ev = await pool.query('SELECT wedding_id FROM project.event WHERE event_id=$1', [event_id]);
-                if (ev.rows.length > 0) await BudgetService.syncWeddingBudget(ev.rows[0].wedding_id);
-            }
-        } catch (e) { console.warn('Budget sync after attendance update failed:', e.message); }
-
-        res.json(result.rows[0]);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// DELETE /api/attendance/:id
-app.delete('/api/attendance/:id', requireAuth, async (req, res) => {
-    try {
-        await pool.query('DELETE FROM project.attendance WHERE attendance_id=$1', [req.params.id]);
-        res.json({ message: 'Attendance record deleted.' });
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// =============================================================
-//  VENUES  (UC0006)
-// =============================================================
-
-// GET /api/venues — all venues with type name
-app.get('/api/venues', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            `SELECT v.*, vt.type_name
-             FROM project.venue v
-             JOIN project.venue_type vt ON v.type_id = vt.type_id
-             ORDER BY v.city, v.name`
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// GET /api/weddings/:wid/venue-bookings
-app.get('/api/weddings/:wid/venue-bookings', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            `SELECT vb.*, v.name AS venue_name, v.location, v.city
-             FROM project.venue_booking vb
-             JOIN project.venue v ON vb.venue_id = v.venue_id
-             WHERE vb.wedding_id = $1
-             ORDER BY vb.date`,
-            [req.params.wid]
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// POST /api/weddings/:wid/venue-bookings
-app.post('/api/weddings/:wid/venue-bookings', requireAuth, async (req, res) => {
-    const { date, start_time, end_time, status, price, venue_id } = req.body;
-    if (!date || !start_time || !end_time || !venue_id)
-        return res.status(400).json({ error: 'date, start_time, end_time and venue_id are required.' });
-
-    // Check availability in a transaction to avoid race conditions
-    const client = await pool.connect();
-    try {
-        console.log('BOOKING REQ venue:', { wedding_id: req.params.wid, date, start_time, end_time, venue_id });
-        await client.query('BEGIN');
-        const dateOnly = BookingService._fmtDate(date);
-        console.log('Normalized dateOnly for venue booking:', dateOnly);
-        // validate wedding date matches
-        const wRes = await client.query('SELECT 1 FROM project.wedding WHERE wedding_id=$1 AND "date" = $2::date', [req.params.wid, dateOnly]);
-        if (wRes.rows.length === 0) {
-            await client.query('ROLLBACK');
-            return res.status(400).json({ error: 'Booking is only allowed on the wedding date.' });
-        }
-
-        // check intra-wedding overlap
-        const bookings = await client.query(
-            `SELECT start_time, end_time FROM project.venue_booking WHERE wedding_id=$1 AND "date" = $2::date`,
-            [req.params.wid, dateOnly]
-        );
-        for (const b of bookings.rows) {
-            if (BookingService.isOverlapping(start_time, end_time, b.start_time, b.end_time)) {
-                await client.query('ROLLBACK');
-                return res.status(409).json({ error: 'This has been already booked, please change date or time to rebook' });
-            }
-        }
-
-        // lock any existing bookings for this venue/date to prevent concurrent inserts
-        const conflictLock = await client.query(
-            `SELECT booking_id FROM project.venue_booking
-             WHERE venue_id=$1 AND "date" = $2::date
-               AND NOT (end_time <= $3 OR start_time >= $4)
-             FOR UPDATE`,
-            [venue_id, dateOnly, start_time, end_time]
-        );
-        if (conflictLock.rows.length > 0) {
-            console.log('CONFLICT detected for venue (locked rows):', conflictLock.rows);
-            await client.query('ROLLBACK');
-            return res.status(409).json({ error: 'Venue is already booked during this time slot.' });
-        }
-
-        // insert booking
-        const ins = await client.query(
-            `INSERT INTO project.venue_booking("date",start_time,end_time,status,price,venue_id,wedding_id)
-             VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING *`,
-            [dateOnly, start_time, end_time, status || 'confirmed', price || 0, venue_id, req.params.wid]
-        );
-
-        await client.query('COMMIT');
-        await BudgetService.syncWeddingBudget(req.params.wid);
-        const _budget = await BudgetService.getBudgetStatus(req.params.wid);
-        res.status(201).json({ ...ins.rows[0], _budget });
-    } catch (err) {
-        try { await client.query('ROLLBACK'); } catch(e) { /* ignore */ }
-        console.error('Venue booking error:', err && err.message ? err.message : err);
-        if (err instanceof ApiError) return res.status(err.status).json({ error: err.message });
-        if (err && err.code === 'P0001') return res.status(409).json({ error: err.message });
-        res.status(500).json({ error: 'Server error.' });
-    } finally {
-        client.release();
-    }
-});
-
-// DELETE /api/venue-bookings/:id
-app.delete('/api/venue-bookings/:id', requireAuth, async (req, res) => {
-    try {
-        const del = await pool.query(
-            'DELETE FROM project.venue_booking WHERE booking_id=$1 RETURNING wedding_id',
-            [req.params.id]
-        );
-        if (del.rows.length > 0) await BudgetService.syncWeddingBudget(del.rows[0].wedding_id);
-        res.json({ message: 'Venue booking cancelled.' });
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// =============================================================
-//  BANDS  (UC0007)
-// =============================================================
-
-// GET /api/bands
-app.get('/api/bands', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query('SELECT * FROM project.band ORDER BY band_name');
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// GET /api/weddings/:wid/band-bookings
-app.get('/api/weddings/:wid/band-bookings', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            `SELECT bb.*, b.band_name, b.genre, b.price_per_hour,
-                    ROUND((EXTRACT(EPOCH FROM (bb.end_time - bb.start_time)) / 3600)::numeric, 2) AS hours,
-                    ROUND((EXTRACT(EPOCH FROM (bb.end_time - bb.start_time)) / 3600 * b.price_per_hour)::numeric, 2) AS booking_cost
-             FROM project.band_booking bb
-             JOIN project.band b ON bb.band_id = b.band_id
-             WHERE bb.wedding_id=$1 ORDER BY bb.date`,
-            [req.params.wid]
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// POST /api/weddings/:wid/band-bookings
-app.post('/api/weddings/:wid/band-bookings', requireAuth, async (req, res) => {
-    const { date, start_time, end_time, status, band_id } = req.body;
-    if (!date || !start_time || !end_time || !band_id)
-        return res.status(400).json({ error: 'date, start_time, end_time and band_id are required.' });
-    const client = await pool.connect();
-    try {
-        console.log('BOOKING REQ band:', { wedding_id: req.params.wid, date, start_time, end_time, band_id });
-        await client.query('BEGIN');
-        const dateOnly = BookingService._fmtDate(date);
-        console.log('Normalized dateOnly for band booking:', dateOnly);
-        // Validate wedding date
-        const wRes = await client.query('SELECT 1 FROM project.wedding WHERE wedding_id=$1 AND "date" = $2::date', [req.params.wid, dateOnly]);
-        if (wRes.rows.length === 0) { await client.query('ROLLBACK'); return res.status(400).json({ error: 'Booking is only allowed on the wedding date.' }); }
-
-        // Intra-wedding overlap
-        const bookings = await client.query('SELECT start_time,end_time FROM project.band_booking WHERE wedding_id=$1 AND "date" = $2::date', [req.params.wid, dateOnly]);
-        for (const b of bookings.rows) {
-            if (BookingService.isOverlapping(start_time, end_time, b.start_time, b.end_time)) {
-                await client.query('ROLLBACK');
-                return res.status(409).json({ error: 'This has been already booked, please change date or time to rebook' });
-            }
-        }
-
-        // Lock overlapping bookings for this band/date across weddings
-        const conflict = await client.query(
-            `SELECT booking_id FROM project.band_booking
-             WHERE band_id=$1 AND "date" = $2::date
-               AND NOT (end_time <= $3 OR start_time >= $4)
-             FOR UPDATE`,
-            [band_id, dateOnly, start_time, end_time]
-        );
-        if (conflict.rows.length > 0) { console.log('CONFLICT detected for band:', conflict.rows); await client.query('ROLLBACK'); return res.status(409).json({ error: 'This has been already booked, please change date or time to rebook' }); }
-
-        console.log('No conflict found for band — inserting booking');
-        const ins = await client.query(
-            `INSERT INTO project.band_booking("date",start_time,end_time,status,band_id,wedding_id)
-             VALUES ($1,$2,$3,$4,$5,$6) RETURNING *`,
-            [dateOnly, start_time, end_time, status || 'confirmed', band_id, req.params.wid]
-        );
-        console.log('Inserted band booking id:', ins.rows[0] && ins.rows[0].booking_id);
-        await client.query('COMMIT');
-        await BudgetService.syncWeddingBudget(req.params.wid);
-        const _budget = await BudgetService.getBudgetStatus(req.params.wid);
-        res.status(201).json({ ...ins.rows[0], _budget });
-    } catch (err) {
-        try { await client.query('ROLLBACK'); } catch (e) { /* ignore */ }
-        console.error('Band booking error:', err && err.message ? err.message : err);
-        if (err instanceof ApiError) return res.status(err.status).json({ error: err.message });
-        if (err && err.code === 'P0001') return res.status(409).json({ error: err.message });
-        res.status(500).json({ error: 'Server error.' });
-    } finally {
-        client.release();
-    }
-});
-
-// DELETE /api/band-bookings/:id
-app.delete('/api/band-bookings/:id', requireAuth, async (req, res) => {
-    try {
-        const del = await pool.query(
-            'DELETE FROM project.band_booking WHERE booking_id=$1 RETURNING wedding_id',
-            [req.params.id]
-        );
-        if (del.rows.length > 0) await BudgetService.syncWeddingBudget(del.rows[0].wedding_id);
-        res.json({ message: 'Band booking removed.' });
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// =============================================================
-//  PHOTOGRAPHERS  (UC0008)
-// =============================================================
-
-// GET /api/photographers
-app.get('/api/photographers', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query('SELECT * FROM project.photographer ORDER BY name');
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// GET /api/weddings/:wid/photographer-bookings
-app.get('/api/weddings/:wid/photographer-bookings', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            `SELECT pb.*, p.name AS photographer_name, p.email AS photographer_email, p.price_per_hour,
-                    ROUND((EXTRACT(EPOCH FROM (pb.end_time - pb.start_time)) / 3600)::numeric, 2) AS hours,
-                    ROUND((EXTRACT(EPOCH FROM (pb.end_time - pb.start_time)) / 3600 * p.price_per_hour)::numeric, 2) AS booking_cost
-             FROM project.photographer_booking pb
-             JOIN project.photographer p ON pb.photographer_id = p.photographer_id
-             WHERE pb.wedding_id=$1 ORDER BY pb.date`,
-            [req.params.wid]
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// POST /api/weddings/:wid/photographer-bookings
-app.post('/api/weddings/:wid/photographer-bookings', requireAuth, async (req, res) => {
-    const { date, start_time, end_time, status, photographer_id } = req.body;
-    if (!date || !start_time || !end_time || !photographer_id)
-        return res.status(400).json({ error: 'date, start_time, end_time and photographer_id are required.' });
-    const client = await pool.connect();
-    try {
-        console.log('BOOKING REQ photographer:', { wedding_id: req.params.wid, date, start_time, end_time, photographer_id });
-        await client.query('BEGIN');
-        const dateOnly = BookingService._fmtDate(date);
-        console.log('Normalized dateOnly for photographer booking:', dateOnly);
-        const wRes = await client.query('SELECT 1 FROM project.wedding WHERE wedding_id=$1 AND "date" = $2::date', [req.params.wid, dateOnly]);
-        if (wRes.rows.length === 0) { await client.query('ROLLBACK'); return res.status(400).json({ error: 'Booking is only allowed on the wedding date.' }); }
-
-        const bookings = await client.query('SELECT start_time,end_time FROM project.photographer_booking WHERE wedding_id=$1 AND "date" = $2::date', [req.params.wid, dateOnly]);
-        for (const b of bookings.rows) {
-            if (BookingService.isOverlapping(start_time, end_time, b.start_time, b.end_time)) {
-                await client.query('ROLLBACK');
-                return res.status(409).json({ error: 'This has been already booked, please change date or time to rebook' });
-            }
-        }
-
-        const conflict = await client.query(
-            `SELECT booking_id FROM project.photographer_booking
-             WHERE photographer_id=$1 AND "date" = $2::date
-               AND NOT (end_time <= $3 OR start_time >= $4)
-             FOR UPDATE`,
-            [photographer_id, dateOnly, start_time, end_time]
-        );
-
-        if (conflict.rows.length > 0) { console.log('CONFLICT detected for photographer:', conflict.rows); await client.query('ROLLBACK'); return res.status(409).json({ error: 'This has been already booked, please change date or time to rebook' }); }
-
-        console.log('No conflict found for photographer — inserting booking');
-        const ins = await client.query(
-            `INSERT INTO project.photographer_booking("date",start_time,end_time,status,photographer_id,wedding_id)
-             VALUES ($1,$2,$3,$4,$5,$6) RETURNING *`,
-            [dateOnly, start_time, end_time, status || 'confirmed', photographer_id, req.params.wid]
-        );
-        console.log('Inserted photographer booking id:', ins.rows[0] && ins.rows[0].booking_id);
-        await client.query('COMMIT');
-        await BudgetService.syncWeddingBudget(req.params.wid);
-        const _budget = await BudgetService.getBudgetStatus(req.params.wid);
-        res.status(201).json({ ...ins.rows[0], _budget });
-    } catch (err) {
-        try { await client.query('ROLLBACK'); } catch (e) { /* ignore */ }
-        console.error('Photographer booking error:', err && err.message ? err.message : err);
-        if (err instanceof ApiError) return res.status(err.status).json({ error: err.message });
-        if (err && err.code === 'P0001') return res.status(409).json({ error: err.message });
-        res.status(500).json({ error: 'Server error.' });
-    } finally {
-        client.release();
-    }
-});
-
-// DELETE /api/photographer-bookings/:id
-app.delete('/api/photographer-bookings/:id', requireAuth, async (req, res) => {
-    try {
-        const del = await pool.query(
-            'DELETE FROM project.photographer_booking WHERE booking_id=$1 RETURNING wedding_id',
-            [req.params.id]
-        );
-        if (del.rows.length > 0) await BudgetService.syncWeddingBudget(del.rows[0].wedding_id);
-        res.json({ message: 'Photographer booking removed.' });
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// =============================================================
-//  CHURCHES + PRIESTS
-// =============================================================
-
-// GET /api/churches — all churches with their linked priest
-app.get('/api/churches', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            `SELECT c.*, p.name AS priest_name, p.contact AS priest_contact
-             FROM project.church c
-             LEFT JOIN project.priest p ON p.church_id = c.church_id
-             ORDER BY c.name`
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// GET /api/weddings/:wid/church-bookings
-app.get('/api/weddings/:wid/church-bookings', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            `SELECT cb.*, c.name AS church_name, c.location, c.contact,
-                    p.name AS priest_name, p.contact AS priest_contact
-             FROM project.church_booking cb
-             JOIN project.church c ON cb.church_id = c.church_id
-             LEFT JOIN project.priest p ON p.church_id = c.church_id
-             WHERE cb.wedding_id=$1 ORDER BY cb.date`,
-            [req.params.wid]
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// POST /api/weddings/:wid/church-bookings
-app.post('/api/weddings/:wid/church-bookings', requireAuth, async (req, res) => {
-    const { date, start_time, end_time, status, church_id } = req.body;
-    if (!date || !start_time || !end_time || !church_id)
-        return res.status(400).json({ error: 'date, start_time, end_time and church_id are required.' });
-    const client = await pool.connect();
-    try {
-        console.log('BOOKING REQ church:', { wedding_id: req.params.wid, date, start_time, end_time, church_id });
-        await client.query('BEGIN');
-        const dateOnly = BookingService._fmtDate(date);
-        console.log('Normalized dateOnly for church booking:', dateOnly);
-        const wRes = await client.query('SELECT 1 FROM project.wedding WHERE wedding_id=$1 AND "date" = $2::date', [req.params.wid, dateOnly]);
-        if (wRes.rows.length === 0) { await client.query('ROLLBACK'); return res.status(400).json({ error: 'Booking is only allowed on the wedding date.' }); }
-
-        const bookings = await client.query('SELECT start_time,end_time FROM project.church_booking WHERE wedding_id=$1 AND "date" = $2::date', [req.params.wid, dateOnly]);
-        for (const b of bookings.rows) {
-            if (BookingService.isOverlapping(start_time, end_time, b.start_time, b.end_time)) {
-                await client.query('ROLLBACK');
-                return res.status(409).json({ error: 'This has been already booked, please change date or time to rebook' });
-            }
-        }
-
-        const conflict = await client.query(
-            `SELECT booking_id FROM project.church_booking
-             WHERE church_id=$1 AND "date" = $2::date
-               AND NOT (end_time <= $3 OR start_time >= $4)
-             FOR UPDATE`,
-            [church_id, dateOnly, start_time, end_time]
-        );
-
-        if (conflict.rows.length > 0) { console.log('CONFLICT detected for church:', conflict.rows); await client.query('ROLLBACK'); return res.status(409).json({ error: 'This has been already booked, please change date or time to rebook' }); }
-
-        console.log('No conflict found for church — inserting booking');
-        const ins = await client.query(
-            `INSERT INTO project.church_booking("date",start_time,end_time,status,church_id,wedding_id)
-             VALUES ($1,$2,$3,$4,$5,$6) RETURNING *`,
-            [dateOnly, start_time, end_time, status || 'confirmed', church_id, req.params.wid]
-        );
-        console.log('Inserted church booking id:', ins.rows[0] && ins.rows[0].booking_id);
-        await client.query('COMMIT');
-        res.status(201).json(ins.rows[0]);
-    } catch (err) {
-        try { await client.query('ROLLBACK'); } catch (e) { /* ignore */ }
-        console.error('Church booking error:', err && err.message ? err.message : err);
-        if (err instanceof ApiError) return res.status(err.status).json({ error: err.message });
-        if (err && err.code === 'P0001') return res.status(409).json({ error: err.message });
-        res.status(500).json({ error: 'Server error.' });
-    } finally {
-        client.release();
-    }
-});
-
-// DELETE /api/church-bookings/:id
-app.delete('/api/church-bookings/:id', requireAuth, async (req, res) => {
-    try {
-        await pool.query('DELETE FROM project.church_booking WHERE booking_id=$1', [req.params.id]);
-        res.json({ message: 'Church booking removed.' });
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// =============================================================
-//  REGISTRARS
-// =============================================================
-
-// GET /api/registrars
-app.get('/api/registrars', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query('SELECT * FROM project.registrar ORDER BY name');
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// GET /api/weddings/:wid/registrar-bookings
-app.get('/api/weddings/:wid/registrar-bookings', requireAuth, async (req, res) => {
-    try {
-        const result = await pool.query(
-            `SELECT rb.*, r.name AS registrar_name, r.location
-             FROM project.registrar_booking rb
-             JOIN project.registrar r ON rb.registrar_id = r.registrar_id
-             WHERE rb.wedding_id=$1 ORDER BY rb.date`,
-            [req.params.wid]
-        );
-        res.json(result.rows);
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// POST /api/weddings/:wid/registrar-bookings
-app.post('/api/weddings/:wid/registrar-bookings', requireAuth, async (req, res) => {
-    const { date, start_time, end_time, status, registrar_id } = req.body;
-    if (!date || !start_time || !end_time || !registrar_id)
-        return res.status(400).json({ error: 'date, start_time, end_time and registrar_id are required.' });
-    const client = await pool.connect();
-    try {
-        console.log('BOOKING REQ registrar:', { wedding_id: req.params.wid, date, start_time, end_time, registrar_id });
-        await client.query('BEGIN');
-        const dateOnly = BookingService._fmtDate(date);
-        console.log('Normalized dateOnly for registrar booking:', dateOnly);
-        const wRes = await client.query('SELECT 1 FROM project.wedding WHERE wedding_id=$1 AND "date" = $2::date', [req.params.wid, dateOnly]);
-        if (wRes.rows.length === 0) { await client.query('ROLLBACK'); return res.status(400).json({ error: 'Booking is only allowed on the wedding date.' }); }
-
-        const bookings = await client.query('SELECT start_time,end_time FROM project.registrar_booking WHERE wedding_id=$1 AND "date" = $2::date', [req.params.wid, dateOnly]);
-        for (const b of bookings.rows) {
-            if (BookingService.isOverlapping(start_time, end_time, b.start_time, b.end_time)) {
-                await client.query('ROLLBACK');
-                return res.status(409).json({ error: 'This has been already booked, please change date or time to rebook' });
-            }
-        }
-
-        const conflict = await client.query(
-            `SELECT booking_id FROM project.registrar_booking
-             WHERE registrar_id=$1 AND "date" = $2::date
-               AND NOT (end_time <= $3 OR start_time >= $4)
-             FOR UPDATE`,
-            [registrar_id, dateOnly, start_time, end_time]
-        );
-        if (conflict.rows.length > 0) { console.log('CONFLICT detected for registrar:', conflict.rows); await client.query('ROLLBACK'); return res.status(409).json({ error: 'This has been already booked, please change date or time to rebook' }); }
-
-        await BookingService.validateRegistrarLocation(req.params.wid, dateOnly, registrar_id);
-
-        console.log('No conflict found for registrar — inserting booking');
-        const ins = await client.query(
-            `INSERT INTO project.registrar_booking("date",start_time,end_time,status,registrar_id,wedding_id)
-             VALUES ($1,$2,$3,$4,$5,$6) RETURNING *`,
-            [dateOnly, start_time, end_time, status || 'confirmed', registrar_id, req.params.wid]
-        );
-        console.log('Inserted registrar booking id:', ins.rows[0] && ins.rows[0].booking_id);
-        await client.query('COMMIT');
-        res.status(201).json(ins.rows[0]);
-    } catch (err) {
-        try { await client.query('ROLLBACK'); } catch (e) { /* ignore */ }
-        console.error('Registrar booking error:', err && err.message ? err.message : err);
-        if (err instanceof ApiError) return res.status(err.status).json({ error: err.message });
-        if (err && err.code === 'P0001') return res.status(409).json({ error: err.message });
-        res.status(500).json({ error: 'Server error.' });
-    } finally {
-        client.release();
-    }
-});
-
-// DELETE /api/registrar-bookings/:id
-app.delete('/api/registrar-bookings/:id', requireAuth, async (req, res) => {
-    try {
-        await pool.query('DELETE FROM project.registrar_booking WHERE booking_id=$1', [req.params.id]);
-        res.json({ message: 'Registrar booking removed.' });
-    } catch (err) {
-        console.error(err.message);
-        res.status(500).json({ error: 'Server error.' });
-    }
-});
-
-// =============================================================
-//  START SERVER
-// =============================================================
-app.listen(PORT, () => {
-    console.log(`\n🌸  Wedding Planner server running at http://localhost:${PORT}`);
-    console.log(`   Dashboard  → http://localhost:${PORT}/Wedding_Planner.html`);
-    console.log(`   Login      → http://localhost:${PORT}/login.html`);
-    console.log(`   RSVP page  → http://localhost:${PORT}/rsvp.html\n`);
-});
-
-// Global error handler — convert ApiError and DB trigger errors into structured JSON responses
-app.use((err, req, res, next) => {
-    console.error('Unhandled error:', err && err.stack ? err.stack : err);
-    if (!err) return res.status(500).json({ error: 'Server error.' });
-    if (err instanceof ApiError) return res.status(err.status).json({ error: err.message });
-    if (err.code === 'P0001' || String(err.message || '').toLowerCase().includes('already booked') || String(err.message || '').toLowerCase().includes('conflict')) {
-        return res.status(409).json({ error: err.message });
-    }
-    return res.status(500).json({ error: 'Server error.' });
-});
