Files
shokuninmarche/server.js
airewit-developer c878eee62b feat: Guest import, RSVP reminders — 7ead7758 scope additions
CSV/Excel Import:
- POST /api/events/:id/guests/import (multer memory storage, max 5MB)
- Accepts .csv and .xlsx/.xls via xlsx + csv-parse/sync
- Handles Hebrew column names and English column names interchangeably
- Phone normalization (domain expert spec): strips spaces/hyphens/parens,
  handles 05X-XXXXXXX → +972..., +972... passthrough, 972... → +972...
  Invalid phone → guest imported with phone=null, warning recorded
- Unknown dietary_preference → 'none'; unknown relationship_group → 'other'
- Bulk insert in transaction (all-or-nothing), max 500 rows
- Returns: { imported, skipped, warnings, details } with per-row reasons
- UTF-8 BOM handled on CSV parse (Excel exports)

RSVP Reminder Cron:
- jobs/reminderCron.js: node-cron, daily at 09:00 Asia/Jerusalem
- Queries guests with rsvp_status=pending where event is 7 or 2 days away
- Regenerates wa.me reminder deep-link with urgency text (עוד שבוע / עוד 2 ימים)
- Updates invitations.whatsapp_link in-place
- No auto-send (MVP): organizer clicks link manually
- Started automatically in server.js app.listen callback

GET /api/events/:id/guests/reminders:
- Returns pending guests who have whatsapp_link set (reminder generated by cron)
- Organizer uses this to surface the Pending Reminders panel

Frontend additions:
- ImportGuestsForm component: file picker, POST multipart, shows import summary
  with per-row skipped/warning details
- PendingRemindersPanel component: orange card listing pending guests with
  wa.me reminder links; hides itself when no reminders
- GuestListPage: integrated both components, refreshTrigger propagates to
  reminders panel after any add/import/delete/status-change

Build: 0 TS errors, 62 modules transformed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 18:35:54 +00:00

48 lines
1.3 KiB
JavaScript

require('dotenv').config();
const express = require('express');
const cookieParser = require('cookie-parser');
const cors = require('cors');
const path = require('path');
const authRoutes = require('./routes/auth');
const guestRoutes = require('./routes/guests');
const rsvpRoutes = require('./routes/rsvp');
const { authMiddleware } = require('./middleware/auth');
const { startReminderCron } = require('./jobs/reminderCron');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors({
origin: process.env.CLIENT_ORIGIN || true,
credentials: true,
}));
app.use(express.json());
app.use(cookieParser());
// Health check — no auth required
app.get('/health', (req, res) => res.json({ status: 'ok' }));
// Auth routes — no middleware (register/login are public)
app.use('/api/auth', authRoutes);
// Public RSVP routes — no auth required
app.use('/api/rsvp', rsvpRoutes);
// All routes below require valid JWT
app.use('/api', authMiddleware);
// Guest management routes (auth enforced above)
app.use('/api', guestRoutes);
// Serve React frontend in production
app.use(express.static(path.join(__dirname, 'client', 'dist')));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'client', 'dist', 'index.html'));
});
app.listen(PORT, () => {
console.log(`אירועית server running on port ${PORT}`);
startReminderCron();
});