- Added real-time event emissions for guest creation, update, and deletion across various API routes. - Enhanced guest request submission to include real-time notifications. - Introduced real-time updates for table creation to keep the admin dashboard synchronized. - Improved error handling for real-time event emissions to ensure reliability during guest and table operations.
563 lines
15 KiB
Markdown
563 lines
15 KiB
Markdown
# Real-Time Data Synchronization - Complete Implementation Guide
|
|
|
|
## 📋 Executive Summary
|
|
|
|
**Status:** Critical fixes completed. Remaining tasks documented for phased implementation.
|
|
|
|
**Key Achievements:**
|
|
- ✅ RSVP → Master Guest List sync fixed
|
|
- ✅ Seating Chart real-time updates added
|
|
- ✅ Reports Summary real-time updates added
|
|
- ✅ Guest update/delete events implemented
|
|
- ✅ Polling intervals standardized
|
|
|
|
**Remaining Work:**
|
|
- ⚠️ Table management routes need POST/PUT/DELETE handlers
|
|
- ⚠️ Some components still use hardcoded polling intervals
|
|
- ⚠️ Guest requests need real-time events
|
|
|
|
---
|
|
|
|
## 🔍 Current Architecture
|
|
|
|
### Real-Time Infrastructure
|
|
- **Technology:** Server-Sent Events (SSE) via `/api/realtime`
|
|
- **Transport:** EventSource API (browser-native, Next.js compatible)
|
|
- **Backend:** Redis Pub/Sub for multi-instance support
|
|
- **Fallback:** In-memory broadcast if Redis unavailable
|
|
- **Auto-reconnect:** Built into EventSource
|
|
|
|
### Event Flow
|
|
```
|
|
API Route (data change)
|
|
↓
|
|
RealtimeManager.sendEvent()
|
|
↓
|
|
POST /api/realtime
|
|
↓
|
|
Redis Pub/Sub (if available)
|
|
↓
|
|
SSE Clients (/api/realtime GET)
|
|
↓
|
|
Client Components (subscribe)
|
|
↓
|
|
UI Updates
|
|
```
|
|
|
|
---
|
|
|
|
## ✅ Phase 1: Critical Fixes (COMPLETED)
|
|
|
|
### 1.1 RSVP Status Sync
|
|
**Problem:** RSVP confirmations didn't update Master Guest List
|
|
|
|
**Solution:**
|
|
- Added `rsvpStatus` field updates in RSVP routes
|
|
- Added `refreshGuestList()` call in `onRSVPUpdate` handler
|
|
- Added real-time events with `rsvpStatus` in payload
|
|
|
|
**Files Modified:**
|
|
- `src/app/api/rsvp/submit/route.ts`
|
|
- `src/app/api/rsvp/update/route.ts`
|
|
- `src/app/api/admin/rsvp/on-behalf/route.ts`
|
|
- `src/components/features/admin/admin-dashboard.tsx`
|
|
|
|
### 1.2 Seating Chart Real-Time
|
|
**Problem:** Seating assignments required manual refresh
|
|
|
|
**Solution:**
|
|
- Added subscriptions to `seating`, `guest`, and `table` events
|
|
- Auto-refresh on table assignment changes
|
|
|
|
**Files Modified:**
|
|
- `src/components/features/admin/seating-chart-enhanced.tsx`
|
|
|
|
### 1.3 Reports Summary Real-Time
|
|
**Problem:** Reports showed stale data
|
|
|
|
**Solution:**
|
|
- Added subscriptions to `rsvp`, `guest`, `seating`, and `table` events
|
|
- Auto-refresh when any relevant data changes
|
|
|
|
**Files Modified:**
|
|
- `src/components/features/admin/reports-exports.tsx`
|
|
|
|
### 1.4 Guest Events
|
|
**Problem:** Guest updates/deletions didn't emit events
|
|
|
|
**Solution:**
|
|
- Added `guest.create`, `guest.update`, `guest.delete` events
|
|
- Added bulk operation events
|
|
|
|
**Files Modified:**
|
|
- `src/app/api/admin/guests/route.ts` (POST, PUT, DELETE)
|
|
- `src/app/api/admin/guests/bulk-update/route.ts`
|
|
- `src/app/api/admin/guests/bulk-delete/route.ts`
|
|
|
|
---
|
|
|
|
## ⚠️ Phase 2: Standardization (TODO)
|
|
|
|
### 2.1 Polling Intervals
|
|
|
|
**Created:** `src/lib/polling-intervals.ts`
|
|
|
|
**Files to Update:**
|
|
1. `src/components/features/dashboard/guest-directory.tsx`
|
|
```typescript
|
|
// Change from: setInterval(loadWithCleanup, 10000)
|
|
// To: setInterval(loadWithCleanup, POLLING_INTERVALS.CRITICAL)
|
|
```
|
|
|
|
2. `src/components/features/admin/guest-requests.tsx`
|
|
```typescript
|
|
// Change from: setInterval(fetchWithCleanup, 30000)
|
|
// To: setInterval(fetchWithCleanup, POLLING_INTERVALS.CRITICAL)
|
|
// Also add real-time subscription
|
|
```
|
|
|
|
3. `src/components/features/admin/activity-feed.tsx`
|
|
```typescript
|
|
// Add real-time + use POLLING_INTERVALS.BACKGROUND
|
|
```
|
|
|
|
### 2.2 Add Real-Time to Guest Requests
|
|
|
|
**File:** `src/components/features/admin/guest-requests.tsx`
|
|
|
|
**Implementation:**
|
|
```typescript
|
|
useEffect(() => {
|
|
const { realtimeManager } = require('@/lib/realtime');
|
|
realtimeManager.connect();
|
|
|
|
const unsubscribe = realtimeManager.subscribe('guest-request', (event) => {
|
|
if (event.action === 'create' || event.action === 'update') {
|
|
fetchWithCleanup();
|
|
}
|
|
});
|
|
|
|
return () => unsubscribe();
|
|
}, []);
|
|
```
|
|
|
|
**Also need:** Add events to `src/app/api/admin/guest-requests/route.ts` (if POST/PUT handlers exist)
|
|
|
|
---
|
|
|
|
## ⚠️ Phase 3: Missing API Routes (TODO)
|
|
|
|
### 3.1 Table Management Routes
|
|
|
|
**Issue:** Component calls POST/PATCH/DELETE on `/api/admin/tables`, but routes don't exist
|
|
|
|
**Files to Create/Update:**
|
|
- `src/app/api/admin/tables/route.ts` - Add POST handler
|
|
- `src/app/api/admin/tables/[id]/route.ts` - Add PATCH and DELETE handlers
|
|
|
|
**Implementation Example:**
|
|
|
|
```typescript
|
|
// src/app/api/admin/tables/route.ts
|
|
|
|
// POST - Create table
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const session = await verifyAdminSession(request);
|
|
if (!session || !session.authenticated) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const body = await request.json();
|
|
const { name, capacity } = body;
|
|
|
|
const newTable = await prisma.table.create({
|
|
data: { name, capacity: capacity || 8 },
|
|
});
|
|
|
|
// Emit real-time event
|
|
try {
|
|
const { RealtimeManager } = await import('@/lib/realtime');
|
|
await RealtimeManager.sendEvent({
|
|
type: 'table',
|
|
action: 'create',
|
|
data: {
|
|
tableId: newTable.id,
|
|
name: newTable.name,
|
|
capacity: newTable.capacity,
|
|
},
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to emit table create event:', error);
|
|
}
|
|
|
|
return NextResponse.json({ success: true, table: newTable }, { status: 201 });
|
|
} catch (error) {
|
|
return NextResponse.json({ error: "Failed to create table" }, { status: 500 });
|
|
}
|
|
}
|
|
```
|
|
|
|
```typescript
|
|
// src/app/api/admin/tables/[id]/route.ts
|
|
|
|
// PATCH - Update table
|
|
export async function PATCH(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
try {
|
|
const session = await verifyAdminSession(request);
|
|
if (!session || !session.authenticated) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const { id } = await params;
|
|
const body = await request.json();
|
|
const { name, capacity } = body;
|
|
|
|
const updatedTable = await prisma.table.update({
|
|
where: { id },
|
|
data: { name, capacity },
|
|
});
|
|
|
|
// Emit real-time event
|
|
try {
|
|
const { RealtimeManager } = await import('@/lib/realtime');
|
|
await RealtimeManager.sendEvent({
|
|
type: 'table',
|
|
action: 'update',
|
|
data: {
|
|
tableId: updatedTable.id,
|
|
name: updatedTable.name,
|
|
capacity: updatedTable.capacity,
|
|
},
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to emit table update event:', error);
|
|
}
|
|
|
|
return NextResponse.json({ success: true, table: updatedTable });
|
|
} catch (error) {
|
|
return NextResponse.json({ error: "Failed to update table" }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
// DELETE - Delete table
|
|
export async function DELETE(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
try {
|
|
const session = await verifyAdminSession(request);
|
|
if (!session || !session.authenticated) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const { id } = await params;
|
|
|
|
const deletedTable = await prisma.table.delete({
|
|
where: { id },
|
|
});
|
|
|
|
// Emit real-time event
|
|
try {
|
|
const { RealtimeManager } = await import('@/lib/realtime');
|
|
await RealtimeManager.sendEvent({
|
|
type: 'table',
|
|
action: 'delete',
|
|
data: {
|
|
tableId: deletedTable.id,
|
|
name: deletedTable.name,
|
|
},
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to emit table delete event:', error);
|
|
}
|
|
|
|
return NextResponse.json({ success: true, message: "Table deleted" });
|
|
} catch (error) {
|
|
return NextResponse.json({ error: "Failed to delete table" }, { status: 500 });
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Component Real-Time Status
|
|
|
|
### Admin Dashboard Components
|
|
|
|
| Component | Real-Time | Polling | Status |
|
|
|-----------|-----------|---------|--------|
|
|
| Master Guest List | ✅ RSVP, Guest | ❌ | ✅ **FIXED** |
|
|
| RSVP Section | ✅ RSVP | ❌ | ✅ Working |
|
|
| Stats | ✅ RSVP | 60s | ✅ Working |
|
|
| Seating Chart | ✅ Seating, Guest, Table | ❌ | ✅ **FIXED** |
|
|
| Reports Summary | ✅ RSVP, Guest, Seating, Table | ❌ | ✅ **FIXED** |
|
|
| Music Requests | ✅ Music | ❌ | ✅ Working |
|
|
| Gallery | ✅ Gallery | ❌ | ✅ Working |
|
|
| Guestbook | ✅ Guestbook | ❌ | ✅ Working |
|
|
| Tributes | ✅ Tribute | ❌ | ✅ Working |
|
|
| Guest Requests | ❌ | 30s | ⚠️ **TODO** |
|
|
| Activity Feed | ❌ | 30s | ⚠️ **TODO** |
|
|
| Who's Who | ✅ RSVP | 30s | ⚠️ Could remove polling |
|
|
|
|
### Guest Dashboard Components
|
|
|
|
| Component | Real-Time | Polling | Status |
|
|
|-----------|-----------|---------|--------|
|
|
| Guest Profile | ✅ RSVP, Seating | 30s | ✅ Working |
|
|
| Table Reveal | ✅ Seating | ❌ | ✅ Working |
|
|
| Music | ✅ Music | 30s | ✅ Working |
|
|
| Gallery | ❌ | ❌ | ⚠️ Should add real-time |
|
|
| Guestbook | ✅ Guestbook | 30s | ✅ Working |
|
|
| Guest Directory | ✅ RSVP, Guest | 10s | ⚠️ Should use 30s |
|
|
| Who's Who | ✅ RSVP | ❌ | ✅ Working |
|
|
|
|
---
|
|
|
|
## 🎯 Implementation Priority
|
|
|
|
### ✅ COMPLETED (Phase 1)
|
|
1. ✅ RSVP → Master Guest List sync
|
|
2. ✅ Seating Chart real-time
|
|
3. ✅ Reports Summary real-time
|
|
4. ✅ Guest update/delete events
|
|
5. ✅ Polling intervals constants
|
|
|
|
### 🔄 IN PROGRESS (Phase 2)
|
|
1. ⚠️ Update polling intervals to use constants
|
|
2. ⚠️ Add real-time to Guest Requests
|
|
3. ⚠️ Add real-time to Activity Feed
|
|
|
|
### 📋 PLANNED (Phase 3)
|
|
1. ⚠️ Add table management routes (POST/PATCH/DELETE)
|
|
2. ⚠️ Add table events
|
|
3. ⚠️ Add guest-request events
|
|
|
|
### 🚀 FUTURE (Phase 4)
|
|
1. ⚠️ Create unified `useRealtimeSync` hook
|
|
2. ⚠️ Refactor components to use unified hook
|
|
3. ⚠️ Add optimistic updates
|
|
4. ⚠️ Add connection status indicators
|
|
|
|
---
|
|
|
|
## 📝 Code Examples
|
|
|
|
### Example 1: Adding Real-Time to a Component
|
|
|
|
```typescript
|
|
// Before
|
|
useEffect(() => {
|
|
fetchData();
|
|
const interval = setInterval(fetchData, 30000);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
// After
|
|
import { POLLING_INTERVALS } from '@/lib/polling-intervals';
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
|
|
// Real-time subscription
|
|
const { realtimeManager } = require('@/lib/realtime');
|
|
realtimeManager.connect();
|
|
|
|
const unsubscribe = realtimeManager.subscribe('your-event-type', (event) => {
|
|
if (event.action === 'create' || event.action === 'update') {
|
|
fetchData();
|
|
}
|
|
});
|
|
|
|
// Polling fallback
|
|
const interval = setInterval(fetchData, POLLING_INTERVALS.CRITICAL);
|
|
|
|
return () => {
|
|
unsubscribe();
|
|
clearInterval(interval);
|
|
};
|
|
}, []);
|
|
```
|
|
|
|
### Example 2: Emitting Events from API Routes
|
|
|
|
```typescript
|
|
// After data modification
|
|
try {
|
|
const { RealtimeManager } = await import('@/lib/realtime');
|
|
await RealtimeManager.sendEvent({
|
|
type: 'your-event-type',
|
|
action: 'create' | 'update' | 'delete',
|
|
data: {
|
|
// Include relevant data for clients
|
|
id: updatedItem.id,
|
|
// ... other fields
|
|
},
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
} catch (error) {
|
|
// Don't fail the API call if real-time fails
|
|
console.error('Failed to emit real-time event:', error);
|
|
}
|
|
```
|
|
|
|
### Example 3: Debounced Real-Time Updates
|
|
|
|
```typescript
|
|
useEffect(() => {
|
|
const { realtimeManager } = require('@/lib/realtime');
|
|
realtimeManager.connect();
|
|
|
|
let debounceTimeout: NodeJS.Timeout | null = null;
|
|
|
|
const unsubscribe = realtimeManager.subscribe('event-type', (event) => {
|
|
// Clear existing timeout
|
|
if (debounceTimeout) clearTimeout(debounceTimeout);
|
|
|
|
// Debounce rapid updates
|
|
debounceTimeout = setTimeout(() => {
|
|
fetchData();
|
|
}, 1000); // 1 second debounce
|
|
});
|
|
|
|
return () => {
|
|
if (debounceTimeout) clearTimeout(debounceTimeout);
|
|
unsubscribe();
|
|
};
|
|
}, []);
|
|
```
|
|
|
|
---
|
|
|
|
## 🧪 Testing Guide
|
|
|
|
### Manual Testing
|
|
|
|
1. **RSVP Sync Test:**
|
|
- Open admin dashboard in two browser windows
|
|
- In window 1: Go to RSVP section
|
|
- In window 2: Go to Master Guest List
|
|
- Have a guest confirm RSVP
|
|
- Verify: Master Guest List updates within 1-2 seconds
|
|
|
|
2. **Seating Chart Test:**
|
|
- Open seating chart
|
|
- Assign guest to table (in another window or via API)
|
|
- Verify: Seating chart updates automatically
|
|
|
|
3. **Reports Test:**
|
|
- Open Reports & Exports
|
|
- Have guest confirm/decline RSVP
|
|
- Verify: Headcount numbers update automatically
|
|
|
|
### Automated Testing
|
|
|
|
```typescript
|
|
// Example test for real-time sync
|
|
describe('RSVP → Master Guest List Sync', () => {
|
|
it('should update guest list when RSVP is confirmed', async () => {
|
|
// 1. Mock real-time event
|
|
// 2. Trigger RSVP confirmation
|
|
// 3. Verify guest list refresh was called
|
|
// 4. Verify guest status updated
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 🔧 Troubleshooting
|
|
|
|
### Issue: Real-Time Not Working
|
|
|
|
**Check:**
|
|
1. SSE connection established? (Check Network tab for `/api/realtime`)
|
|
2. Redis available? (Check server logs)
|
|
3. Events being emitted? (Check API route logs)
|
|
4. Components subscribed? (Check component useEffect)
|
|
|
|
### Issue: Too Many Refreshes
|
|
|
|
**Solution:** Increase debounce time or add more specific event filtering
|
|
|
|
### Issue: Stale Data
|
|
|
|
**Solution:**
|
|
1. Check cache invalidation tags
|
|
2. Verify real-time events include all necessary data
|
|
3. Ensure polling fallback is working
|
|
|
|
---
|
|
|
|
## 📈 Performance Considerations
|
|
|
|
### Current Optimizations
|
|
- ✅ Debouncing (500ms-2000ms)
|
|
- ✅ Event filtering (only subscribe to needed events)
|
|
- ✅ Parallel event emission (bulk operations)
|
|
- ✅ Non-blocking (events don't fail API calls)
|
|
|
|
### Recommendations
|
|
- Monitor SSE connection count
|
|
- Track event emission rate
|
|
- Consider batching events for bulk operations
|
|
- Add connection pooling if needed
|
|
|
|
---
|
|
|
|
## 🎓 Best Practices
|
|
|
|
1. **Always use debouncing** for rapid updates
|
|
2. **Keep polling as fallback** - real-time can fail
|
|
3. **Don't block API responses** - emit events asynchronously
|
|
4. **Include relevant data** in events - avoid unnecessary refetches
|
|
5. **Clean up subscriptions** - prevent memory leaks
|
|
6. **Handle errors gracefully** - real-time failures shouldn't break features
|
|
|
|
---
|
|
|
|
## 📚 Reference
|
|
|
|
- **Real-Time Manager:** `src/lib/realtime.ts`
|
|
- **SSE Endpoint:** `src/app/api/realtime/route.ts`
|
|
- **Admin Realtime Hook:** `src/hooks/use-admin-realtime.ts`
|
|
- **Polling Constants:** `src/lib/polling-intervals.ts`
|
|
- **Full Audit:** `REALTIME_AUDIT.md`
|
|
|
|
---
|
|
|
|
## ✅ Completion Checklist
|
|
|
|
### Critical Fixes
|
|
- [x] RSVP → Master Guest List sync
|
|
- [x] Seating Chart real-time
|
|
- [x] Reports Summary real-time
|
|
- [x] Guest update/delete events
|
|
- [x] Polling intervals constants
|
|
|
|
### Standardization
|
|
- [ ] Update all polling intervals
|
|
- [ ] Add real-time to Guest Requests
|
|
- [ ] Add real-time to Activity Feed
|
|
|
|
### Missing Routes
|
|
- [ ] Add table POST/PATCH/DELETE routes
|
|
- [ ] Add table events
|
|
- [ ] Add guest-request events
|
|
|
|
### Enhancement
|
|
- [ ] Create unified hook
|
|
- [ ] Refactor components
|
|
- [ ] Add connection status UI
|
|
- [ ] Performance testing
|
|
|
|
---
|
|
|
|
**Last Updated:** Based on comprehensive audit and critical fixes implementation.
|