226 lines
7.2 KiB
TypeScript
226 lines
7.2 KiB
TypeScript
import express, { Request, Response } from 'express';
|
|
import cors from 'cors';
|
|
import sqlite3 from 'sqlite3';
|
|
import { open } from 'sqlite';
|
|
import dotenv from 'dotenv';
|
|
import path from 'path';
|
|
|
|
dotenv.config();
|
|
|
|
const app = express();
|
|
const port = process.env.PORT || 3000;
|
|
|
|
// Middleware
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
|
|
// Serve static files from the frontend build directory
|
|
app.use(express.static(path.join(__dirname, '../frontend/build')));
|
|
|
|
// Database connection
|
|
let db: any;
|
|
|
|
async function connectToDatabase() {
|
|
db = await open({
|
|
filename: './database.sqlite',
|
|
driver: sqlite3.Database
|
|
});
|
|
}
|
|
|
|
// Routes
|
|
app.get('/api/events', async (req: Request, res: Response) => {
|
|
try {
|
|
const rows = await db.all('SELECT * FROM events');
|
|
res.json(rows);
|
|
} catch (error) {
|
|
console.error('Error fetching events:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/events/:slug', async (req: Request, res: Response) => {
|
|
try {
|
|
const { slug } = req.params;
|
|
const rows = await db.all('SELECT * FROM events WHERE slug = ?', [slug]);
|
|
if (rows.length === 0) {
|
|
return res.status(404).json({ error: 'Event not found' });
|
|
}
|
|
res.json(rows[0]);
|
|
} catch (error) {
|
|
console.error('Error fetching event:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
app.post('/api/events', async (req: Request, res: Response) => {
|
|
try {
|
|
const { title, description, date, location, needed_items } = req.body;
|
|
// Generate a slug from the title
|
|
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
|
|
const result = await db.run(
|
|
'INSERT INTO events (title, description, date, location, slug, needed_items) VALUES (?, ?, ?, ?, ?, ?)',
|
|
[title, description, date, location, slug, JSON.stringify(needed_items || [])]
|
|
);
|
|
res.status(201).json({ ...result, slug });
|
|
} catch (error) {
|
|
console.error('Error creating event:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/events/:slug', async (req: Request, res: Response) => {
|
|
try {
|
|
const { slug } = req.params;
|
|
|
|
// First check if the event exists
|
|
const eventRows = await db.all('SELECT id FROM events WHERE slug = ?', [slug]);
|
|
|
|
if (eventRows.length === 0) {
|
|
return res.status(404).json({ error: 'Event not found' });
|
|
}
|
|
|
|
// Delete the event (RSVPs will be automatically deleted due to ON DELETE CASCADE)
|
|
await db.run('DELETE FROM events WHERE slug = ?', [slug]);
|
|
|
|
res.status(204).send();
|
|
} catch (error) {
|
|
console.error('Error deleting event:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// RSVP routes
|
|
app.get('/api/events/:slug/rsvps', async (req: Request, res: Response) => {
|
|
try {
|
|
const { slug } = req.params;
|
|
const eventRows = await db.all('SELECT id FROM events WHERE slug = ?', [slug]);
|
|
|
|
if (eventRows.length === 0) {
|
|
return res.status(404).json({ error: 'Event not found' });
|
|
}
|
|
|
|
const eventId = eventRows[0].id;
|
|
const rows = await db.all('SELECT * FROM rsvps WHERE event_id = ?', [eventId]);
|
|
res.json(rows);
|
|
} catch (error) {
|
|
console.error('Error fetching RSVPs:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
app.post('/api/events/:slug/rsvp', async (req: Request, res: Response) => {
|
|
try {
|
|
const { slug } = req.params;
|
|
const { name, attending, bringing_guests, guest_count, guest_names, items_bringing } = req.body;
|
|
|
|
const eventRows = await db.all('SELECT id FROM events WHERE slug = ?', [slug]);
|
|
|
|
if (eventRows.length === 0) {
|
|
return res.status(404).json({ error: 'Event not found' });
|
|
}
|
|
|
|
const eventId = eventRows[0].id;
|
|
const result = await db.run(
|
|
'INSERT INTO rsvps (event_id, name, attending, bringing_guests, guest_count, guest_names, items_bringing) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
[eventId, name, attending, bringing_guests, guest_count, guest_names, JSON.stringify(items_bringing || [])]
|
|
);
|
|
res.status(201).json(result);
|
|
} catch (error) {
|
|
console.error('Error creating RSVP:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/events/:slug/rsvps/:id', async (req: Request, res: Response) => {
|
|
try {
|
|
const { slug, id } = req.params;
|
|
|
|
// Verify the RSVP belongs to the correct event
|
|
const eventRows = await db.all('SELECT id FROM events WHERE slug = ?', [slug]);
|
|
|
|
if (eventRows.length === 0) {
|
|
return res.status(404).json({ error: 'Event not found' });
|
|
}
|
|
|
|
const eventId = eventRows[0].id;
|
|
await db.run('DELETE FROM rsvps WHERE id = ? AND event_id = ?', [id, eventId]);
|
|
res.status(204).send();
|
|
} catch (error) {
|
|
console.error('Error deleting RSVP:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Update RSVP
|
|
app.put('/api/events/:slug/rsvps/:id', async (req: Request, res: Response) => {
|
|
try {
|
|
const { slug, id } = req.params;
|
|
const { name, attending, bringing_guests, guest_count, guest_names, items_bringing } = req.body;
|
|
|
|
// Verify the RSVP belongs to the correct event
|
|
const eventRows = await db.all('SELECT id FROM events WHERE slug = ?', [slug]);
|
|
|
|
if (eventRows.length === 0) {
|
|
return res.status(404).json({ error: 'Event not found' });
|
|
}
|
|
|
|
const eventId = eventRows[0].id;
|
|
await db.run(
|
|
'UPDATE rsvps SET name = ?, attending = ?, bringing_guests = ?, guest_count = ?, guest_names = ?, items_bringing = ? WHERE id = ? AND event_id = ?',
|
|
[name, attending, bringing_guests, guest_count, guest_names, JSON.stringify(items_bringing || []), id, eventId]
|
|
);
|
|
res.status(200).json({ message: 'RSVP updated successfully' });
|
|
} catch (error) {
|
|
console.error('Error updating RSVP:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
// Initialize database tables
|
|
async function initializeDatabase() {
|
|
try {
|
|
await db.exec(`
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
title TEXT NOT NULL,
|
|
description TEXT,
|
|
date TEXT NOT NULL,
|
|
location TEXT,
|
|
slug TEXT NOT NULL UNIQUE,
|
|
needed_items TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`);
|
|
|
|
await db.exec(`
|
|
CREATE TABLE IF NOT EXISTS rsvps (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
event_id INTEGER NOT NULL,
|
|
name TEXT NOT NULL,
|
|
attending TEXT NOT NULL,
|
|
bringing_guests TEXT NOT NULL,
|
|
guest_count INTEGER DEFAULT 0,
|
|
guest_names TEXT,
|
|
items_bringing TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE
|
|
)
|
|
`);
|
|
console.log('Database initialized successfully');
|
|
} catch (error) {
|
|
console.error('Error initializing database:', error);
|
|
}
|
|
}
|
|
|
|
// Handle client-side routing
|
|
app.get('*', (req: Request, res: Response) => {
|
|
res.sendFile(path.join(__dirname, '../frontend/build/index.html'));
|
|
});
|
|
|
|
// Start server
|
|
app.listen(port, async () => {
|
|
console.log(`Server running on port ${port}`);
|
|
await connectToDatabase();
|
|
await initializeDatabase();
|
|
});
|