Skip to content

Commit 2ae06c2

Browse files
committed
Refactor sync-events workflow to install dependencies directly in scripts directory and improve error handling in event fetching
1 parent 590d0a6 commit 2ae06c2

File tree

2 files changed

+158
-127
lines changed

2 files changed

+158
-127
lines changed

.github/workflows/sync-events.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,13 @@ jobs:
3131
node-version: '20'
3232

3333
- name: Install dependencies
34-
run: |
35-
mkdir /tmp/sync-deps
36-
cd /tmp/sync-deps
37-
npm install axios rss-parser
34+
run: npm install --prefix scripts axios rss-parser
3835

3936
- name: Fetch and sync events
4037
env:
4138
MEETUP_ACCESS_TOKEN: ${{ secrets.MEETUP_ACCESS_TOKEN }}
4239
TICKET_TAILOR_API_KEY: ${{ secrets.TICKET_TAILOR_API_KEY }}
43-
run: NODE_PATH=/tmp/sync-deps/node_modules node scripts/sync-events.js
40+
run: cd scripts && node sync-events.js
4441

4542
- name: Commit and push if changed
4643
run: |

scripts/sync-events.js

Lines changed: 156 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,181 @@
1-
import fs from 'fs';
2-
import Parser from 'rss-parser';
3-
import axios from 'axios';
1+
import fs from "fs";
2+
import Parser from "rss-parser";
3+
import axios from "axios";
4+
5+
import { fileURLToPath } from "url";
6+
import path from "path";
47

58
const parser = new Parser();
69

710
async function fetchMeetupEvents() {
8-
try {
9-
console.log('📅 Fetching Meetup events via RSS feed...');
10-
const feed = await parser.parseURL('https://www.meetup.com/lighthouselive//events/rss/');
11-
12-
const events = [];
13-
14-
const dateRegex = /(\b\d{2})\.(\d{2})\.(\d{4})\b/; // DD.MM.YYYY
15-
const timeRegex = /(\b\d{1,2}:\d{2}\b)/; // HH:MM
16-
17-
for (const item of feed.items) {
18-
const content = item.contentSnippet || item.content || '';
19-
20-
let eventDate = null;
21-
const dateMatch = content.match(dateRegex);
22-
if (dateMatch) {
23-
const [_, day, month, year] = dateMatch;
24-
eventDate = new Date(`${year}-${month}-${day}T00:00:00`);
25-
if (eventDate <= new Date()) continue; // filter past events
26-
}
27-
28-
let timeText = '';
29-
const timeMatch = content.match(timeRegex);
30-
if (timeMatch) timeText = timeMatch[0];
31-
32-
events.push({
33-
type: "Meetup",
34-
title: item.title,
35-
date: eventDate ? eventDate.toISOString() : null,
36-
displayDate: eventDate ? formatDate(eventDate) : 'TBD',
37-
time: timeText,
38-
location: 'Online',
39-
price: 'Free',
40-
description: content.substring(0, 300) + '...',
41-
ctaText: 'RSVP on Meetup',
42-
ctaLink: item.link,
43-
source: 'meetup'
44-
});
45-
}
46-
47-
console.log(`✅ Fetched ${events.length} Meetup events`);
48-
return events;
49-
} catch (error) {
50-
console.error('❌ Meetup RSS error:', error.message);
51-
return [];
52-
}
11+
try {
12+
console.log("📅 Fetching Meetup events via RSS feed...");
13+
const feed = await parser.parseURL(
14+
"https://www.meetup.com/lighthouselive//events/rss/",
15+
);
16+
17+
const events = [];
18+
19+
const dateRegex = /(\b\d{2})\.(\d{2})\.(\d{4})\b/; // DD.MM.YYYY
20+
const timeRegex = /(\b\d{1,2}:\d{2}\b)/; // HH:MM
21+
22+
for (const item of feed.items) {
23+
const content = item.contentSnippet || item.content || "";
24+
25+
let eventDate = null;
26+
const dateMatch = content.match(dateRegex);
27+
if (dateMatch) {
28+
const [_, day, month, year] = dateMatch;
29+
eventDate = new Date(`${year}-${month}-${day}T00:00:00`);
30+
if (eventDate <= new Date()) continue; // filter past events
31+
}
32+
33+
let timeText = "";
34+
const timeMatch = content.match(timeRegex);
35+
if (timeMatch) timeText = timeMatch[0];
36+
37+
events.push({
38+
type: "Meetup",
39+
title: item.title,
40+
date: eventDate ? eventDate.toISOString() : null,
41+
displayDate: eventDate ? formatDate(eventDate) : "TBD",
42+
time: timeText,
43+
location: "Online",
44+
price: "Free",
45+
description: content.substring(0, 300) + "...",
46+
ctaText: "RSVP on Meetup",
47+
ctaLink: item.link,
48+
source: "meetup",
49+
});
50+
}
51+
52+
console.log(`✅ Fetched ${events.length} Meetup events`);
53+
return events;
54+
} catch (error) {
55+
console.error("❌ Meetup RSS error:", error.message);
56+
return [];
57+
}
5358
}
5459

5560
async function fetchTicketTailorEvents() {
56-
try {
57-
const TICKET_TAILOR_API_KEY = process.env.TICKET_TAILOR_API_KEY;
58-
if (!TICKET_TAILOR_API_KEY) return [];
59-
60-
const response = await axios.get('https://api.tickettailor.com/v1/events', {
61-
params: { status: 'published', start_at_gte: new Date().toISOString() },
62-
headers: { Authorization: `Basic ${Buffer.from(TICKET_TAILOR_API_KEY + ':').toString('base64')}`, Accept: 'application/json' }
63-
});
64-
65-
const now = new Date();
66-
return response.data.data
67-
.filter(event => new Date(event.start.date) > now)
68-
.map(event => {
69-
const eventDate = new Date(event.start.date);
70-
const prices = event.ticket_types?.map(t => t.price / 100) || [];
71-
const minPrice = prices.length > 0 ? Math.min(...prices) : null;
72-
const maxPrice = prices.length > 0 ? Math.max(...prices) : null;
73-
74-
// Map currency codes to symbols
75-
const currencySymbol = {
76-
'eur': '€',
77-
'usd': '$',
78-
'chf': 'CHF'
79-
}[event.currency?.toLowerCase()] || '$';
80-
81-
let priceDisplay = 'Free';
82-
if (minPrice && maxPrice) priceDisplay = minPrice === maxPrice ? `${currencySymbol}${minPrice}` : `${currencySymbol}${minPrice} - ${currencySymbol}${maxPrice}`;
83-
84-
return {
85-
type: determineEventType(event.description),
86-
title: event.name,
87-
date: event.start.date,
88-
displayDate: formatDate(eventDate),
89-
time: `${formatTime(eventDate)} - ${formatTime(new Date(event.end.date))} ${event.start.timezone}`,
90-
location: event.venue?.name || 'Online',
91-
price: priceDisplay,
92-
description: stripHtml(event.description).substring(0, 300) + '...',
93-
ctaText: 'Buy Tickets',
94-
ctaLink: event.url,
95-
source: 'tickettailor'
96-
};
97-
});
98-
} catch (error) {
99-
console.error('❌ Ticket Tailor API error:', error.message);
100-
return [];
101-
}
61+
try {
62+
const TICKET_TAILOR_API_KEY = process.env.TICKET_TAILOR_API_KEY;
63+
if (!TICKET_TAILOR_API_KEY) return [];
64+
65+
const response = await axios.get("https://api.tickettailor.com/v1/events", {
66+
params: { status: "published", start_at_gte: new Date().toISOString() },
67+
headers: {
68+
Authorization: `Basic ${Buffer.from(TICKET_TAILOR_API_KEY + ":").toString("base64")}`,
69+
Accept: "application/json",
70+
},
71+
});
72+
73+
const now = new Date();
74+
return response.data.data
75+
.filter((event) => new Date(event.start.date) > now)
76+
.map((event) => {
77+
const eventDate = new Date(event.start.date);
78+
const prices = event.ticket_types?.map((t) => t.price / 100) || [];
79+
const minPrice = prices.length > 0 ? Math.min(...prices) : null;
80+
const maxPrice = prices.length > 0 ? Math.max(...prices) : null;
81+
82+
// Map currency codes to symbols
83+
const currencySymbol =
84+
{
85+
eur: "€",
86+
usd: "$",
87+
chf: "CHF",
88+
}[event.currency?.toLowerCase()] || "$";
89+
90+
let priceDisplay = "Free";
91+
if (minPrice && maxPrice)
92+
priceDisplay =
93+
minPrice === maxPrice
94+
? `${currencySymbol}${minPrice}`
95+
: `${currencySymbol}${minPrice} - ${currencySymbol}${maxPrice}`;
96+
97+
return {
98+
type: determineEventType(event.description),
99+
title: event.name,
100+
date: event.start.date,
101+
displayDate: formatDate(eventDate),
102+
time: `${formatTime(eventDate)} - ${formatTime(new Date(event.end.date))} ${event.start.timezone}`,
103+
location: event.venue?.name || "Online",
104+
price: priceDisplay,
105+
description: stripHtml(event.description).substring(0, 300) + "...",
106+
ctaText: "Buy Tickets",
107+
ctaLink: event.url,
108+
source: "tickettailor",
109+
};
110+
});
111+
} catch (error) {
112+
console.error("❌ Ticket Tailor API error:", error.message);
113+
return [];
114+
}
102115
}
103116

104117
function determineEventType(title) {
105-
const titleLower = title.toLowerCase();
106-
if (titleLower.includes('workshop')) return ['Workshop'];
107-
if (titleLower.includes('training') || titleLower.includes('course')) return ['Training'];
108-
if (titleLower.includes('talk') || titleLower.includes('presentation')) return ['Talk'];
109-
if (titleLower.includes('conference') || titleLower.includes('summit')) return ['Conference'];
110-
return ['Talk'];
118+
const titleLower = title.toLowerCase();
119+
if (titleLower.includes("workshop")) return ["Workshop"];
120+
if (titleLower.includes("training") || titleLower.includes("course"))
121+
return ["Training"];
122+
if (titleLower.includes("talk") || titleLower.includes("presentation"))
123+
return ["Talk"];
124+
if (titleLower.includes("conference") || titleLower.includes("summit"))
125+
return ["Conference"];
126+
return ["Talk"];
111127
}
112128

113129
function formatDate(date) {
114-
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
130+
return date.toLocaleDateString("en-US", {
131+
month: "short",
132+
day: "numeric",
133+
year: "numeric",
134+
});
115135
}
116136

117137
function formatTime(date) {
118-
return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
138+
return date.toLocaleTimeString("en-US", {
139+
hour: "numeric",
140+
minute: "2-digit",
141+
hour12: true,
142+
});
119143
}
120144

121145
function stripHtml(html) {
122-
if (!html) return '';
123-
return html.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
146+
if (!html) return "";
147+
return html
148+
.replace(/<[^>]*>/g, "")
149+
.replace(/\s+/g, " ")
150+
.trim();
124151
}
125152

126153
async function main() {
127-
console.log('🚀 Starting event sync...\n');
128-
129-
const [meetupEvents, ticketTailorEvents] = await Promise.all([fetchMeetupEvents(), fetchTicketTailorEvents()]);
130-
const allEvents = [...meetupEvents, ...ticketTailorEvents].sort((a, b) => {
131-
if (!a.date) return 1;
132-
if (!b.date) return -1;
133-
return new Date(a.date) - new Date(b.date);
134-
});
135-
136-
const output = { events: allEvents };
137-
fs.writeFileSync('public/events-data.json', JSON.stringify(output, null, 2));
138-
139-
console.log(`\n✨ Successfully synced ${allEvents.length} total events`);
140-
console.log(` - Meetup: ${meetupEvents.length}`);
141-
console.log(` - Ticket Tailor: ${ticketTailorEvents.length}`);
154+
console.log("🚀 Starting event sync...\n");
155+
156+
const [meetupEvents, ticketTailorEvents] = await Promise.all([
157+
fetchMeetupEvents(),
158+
fetchTicketTailorEvents(),
159+
]);
160+
const allEvents = [...meetupEvents, ...ticketTailorEvents].sort((a, b) => {
161+
if (!a.date) return 1;
162+
if (!b.date) return -1;
163+
return new Date(a.date) - new Date(b.date);
164+
});
165+
166+
const output = { events: allEvents };
167+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
168+
fs.writeFileSync(
169+
path.join(__dirname, "../public/events-data.json"),
170+
JSON.stringify(output, null, 2),
171+
);
172+
173+
console.log(`\n✨ Successfully synced ${allEvents.length} total events`);
174+
console.log(` - Meetup: ${meetupEvents.length}`);
175+
console.log(` - Ticket Tailor: ${ticketTailorEvents.length}`);
142176
}
143177

144-
main().catch(error => {
145-
console.error('❌ Fatal error:', error);
146-
process.exit(1);
178+
main().catch((error) => {
179+
console.error("❌ Fatal error:", error);
180+
process.exit(1);
147181
});

0 commit comments

Comments
 (0)