Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Minor Fix for Event Data Fetching#60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Domin0de merged 4 commits intoqafromI2-17/events-api
Jul 10, 2025
Merged
Show file tree
Hide file tree
Changes from1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PrevPrevious commit
NextNext commit
polished code
  • Loading branch information
Yankai Zhu committedJun 16, 2025
commit36b5791a8b16443c9d1decd10af6d3f0412df6a7
14 changes: 14 additions & 0 deletionsbackend/package-lock.json
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

1 change: 1 addition & 0 deletionsbackend/package.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^20.12.13",
"async-mutex": "^0.5.0",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
Expand Down
104 changes: 67 additions & 37 deletionsbackend/src/controllers/eventsWebhook.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
import crypto from'crypto';
import crypto from"crypto";
import { RequestHandler } from "express";
import { eventInfo, fetchEvent, filterInPlace, replaceInPlace } from '../data/eventData';

interface ChangesEntry {
field: string;
value: {
event_id: string;
item: string;
verb: string;
}
}
import { eventInfo, eventInfoMutex, fetchEvent } from "../data/eventData";
import { filterInPlace, replaceInPlace } from "../util";

interface FacebookWebhookNotificationEntry {
id: string;
changes: ChangesEntry[];
}

interface FacebookWebhookNotification {
entry: FacebookWebhookNotificationEntry[];
interface FacebookWebhookPayload {
object: string;
entry: Array<{
id: string;
changes: Array<{
field: string;
value: {
event_id: string;
item: string;
verb: string;
};
}>;
}>;
}

const verifySignature = (rawBody: Buffer, signatureHeader?: string): boolean => {
const verifySignature = (
rawBody: Buffer,
signatureHeader?: string
): boolean => {
if (!signatureHeader) return false;
const [algo, signature] = signatureHeader.split('=');
if (algo !=='sha256') return false;
const [algo, signature] = signatureHeader.split("=");
if (algo !=="sha256") return false;

const expected = crypto
.createHmac('sha256', process.env.FB_APP_SECRET as string)
.createHmac("sha256", process.env.FB_APP_SECRET as string)
.update(rawBody)
.digest('hex');
.digest("hex");

return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
};

export const EventsWebhookVerifier: RequestHandler = (req, res) => {
const mode = req.query["hub.mode"];
Expand DownExpand Up@@ -73,13 +73,22 @@ https://developers.facebook.com/docs/graph-api/webhooks/reference/page/#feed --
*/

export const EventsWebhookUpdate: RequestHandler = async (req, res) => {
const signature = req.headers['x-hub-signature-256'];
if (!req.rawBody || typeof signature !== "string" || !verifySignature(req.rawBody, signature)) {
const signature = req.headers["x-hub-signature-256"];
if (
!req.rawBody ||
typeof signature !== "string" ||
!verifySignature(req.rawBody, signature)
) {
return res.sendStatus(401);
}

const notif: FacebookWebhookNotification = req.body;
if (!notif || !notif.entry || notif.object !== "page" || notif.entry.length === 0) {
const notif: FacebookWebhookPayload = req.body;
if (
!notif ||
!notif.entry ||
notif.object !== "page" ||
notif.entry.length === 0
) {
return res.sendStatus(400);
}

Expand All@@ -89,19 +98,40 @@ export const EventsWebhookUpdate: RequestHandler = async (req, res) => {
for (const change of entry.changes) {
if (change.field !== "feed" || change.value.item !== "event") continue;

if (change.value.verb === "delete") {
// we need filter *in place* because all imports are immutable (the REAL const)
filterInPlace(eventInfo, (val, index, arr) => val.id !== change.value.event_id);
} else {
try {
try {
if (change.value.verb === "delete") {
await eventInfoMutex.runExclusive(() =>
filterInPlace(eventInfo, (val) => val.id !== change.value.event_id)
);
console.log(`Deleted event: ${change.value.event_id}`);
} else if (change.value.verb === "edit") {
const newEvent = await fetchEvent(change.value.event_id);

eventInfoMutex.runExclusive(() =>
replaceInPlace(
eventInfo,
(val) => val.id === change.value.event_id,
newEvent
)
);
console.log(`Edited event: ${change.value.event_id}`);
} else if (change.value.verb === "add") {
const newEvent = await fetchEvent(change.value.event_id);
replaceInPlace(eventInfo, (val, index, arr) => val.id === change.value.event_id, newEvent);
} catch(err) {
console.log(`Wasn't able to update event for some reason: ${err}`);
await eventInfoMutex.runExclusive(() => eventInfo.push(newEvent));
console.log(`Added event: ${change.value.event_id}`);
} else {
console.warn(
`Unknown verb "${change.value.verb}" for event ${change.value.event_id}`
);
}
} catch (err) {
console.error(
`Error processing event: ${change.value.event_id}:\n${err}`
);
return res.sendStatus(500);
}
}
}

res.sendStatus(200);
}
};
82 changes: 31 additions & 51 deletionsbackend/src/data/eventData.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
import { Mutex } from "async-mutex";
import { inspect } from "util";
import { FacebookError, Result, ResultType } from "../util";

class EventInfo {
// god forbid a class have public members
public id: string;
Expand DownExpand Up@@ -28,39 +32,6 @@ class EventInfo {
}
}

// We are altering the array in place, pray we do not alter it from another thread
// I don't even know if concurrent modification exception is a thing in JS
// Maybe this is a single threaded moment :icant:
export function filterInPlace<T>(
arr: T[],
predicate: (value: T, index: number, array: T[]) => boolean
): T[] {
let write = 0;
for (let read = 0; read < arr.length; read++) {
const val = arr[read];
if (predicate(val, read, arr)) {
arr[write++] = val;
}
}
arr.length = write;
return arr;
}

// This one is definitely not thread safe lmao
// TODO fix with a mutex probably
export function replaceInPlace<T>(
arr: T[],
predicate: (value: T, index: number, array: T[]) => boolean,
replacement: T
): number {
const idx = arr.findIndex(predicate);
if (idx !== -1) arr[idx] = replacement;
return idx;
}

// we LOVE global variables
export let eventInfo: EventInfo[] = [];

interface FacebookEvent {
id: string;
name: string;
Expand All@@ -76,51 +47,60 @@ interface FacebookEventsResponse {

// this isn't in .env for different module compatiblity
const FB_API_VERSION = "v23.0";
const DEFAULT_EVENT_LOCATION = "Everything everywhere all at once!!!";
const DEFAULT_EVENT_IMAGE = "/images/events/default_event.jpg";

// we LOVE global variables
export const eventInfoMutex = new Mutex();
export const eventInfo: EventInfo[] = [];

export async function fetchEvents() {
const response = await fetch(
`https://graph.facebook.com/${FB_API_VERSION}/${process.env.FB_EVENT_PAGE_ID}/events?access_token=${process.env.FB_ACCESS_TOKEN}&fields=id,name,cover,place,start_time,end_time`
);

const res: FacebookEventsResponse = await response.json();

if (!res || !res.data) {
console.log("No events found...");
return;
const res: Result<FacebookEventsResponse, FacebookError> = await response.json();
if (!res || res.type === ResultType.Err) {
console.log(`No events found...\n${res}`);
return [];
}

const processed = res.data.map(
const processed = res.value.data.map(
(e) =>
new EventInfo(
e.id,
e.name,
e.start_time,
e.end_time,
e.place?.name ??"Everything everywhere all at once!!!",
e.cover?.source|| "/images/events/default_event.jpg"
e.place?.name ??DEFAULT_EVENT_LOCATION,
e.cover?.source?? DEFAULT_EVENT_IMAGE
)
);

eventInfo = processed;
return processed;
}

export async function fetchEvent(id: string) {
const response = await fetch(
`https://graph.facebook.com/${FB_API_VERSION}/${id}?access_token=${process.env.FB_ACCESS_TOKEN}&fields=id,name,cover,place,start_time,end_time`
);

const res: FacebookEvent = await response.json();
const res:Result<FacebookEvent, FacebookError> = await response.json();

if (!res) {
throw new Error(`Couldn't get details for event ${id}`);
if (!res || res.type === ResultType.Err) {
throw new Error(
`Couldn't fetch details for event ${id}\n${inspect(
Object.getOwnPropertyDescriptor(res, "error")?.value
)}`
);
}

return new EventInfo(
res.id,
res.name,
res.start_time,
res.end_time,
res.place?.name ??"Everything everywhere all at once!!!",
res.cover?.source|| "/images/events/default_event.jpg"
res.value.id,
res.value.name,
res.value.start_time,
res.value.end_time,
res.value.place?.name ??DEFAULT_EVENT_LOCATION,
res.value.cover?.source?? DEFAULT_EVENT_IMAGE
);
}
14 changes: 8 additions & 6 deletionsbackend/src/index.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,22 +4,24 @@ import dotenv from "dotenv";
import pingRoute from "./routes/ping";
import eventsRoute from "./routes/events";
import eventsWebhookRoute from "./routes/eventsWebhook";
import { fetchEvents } from "./data/eventData";
import {eventInfo, eventInfoMutex,fetchEvents } from "./data/eventData";

dotenv.config();

(async () => {
try {
await fetchEvents();
const events = await fetchEvents();
eventInfoMutex.runExclusive(() => eventInfo.concat(events));
console.log("Events fetched successfully");
} catch (error) {
// do we ungracefully bail out here???
console.error("Error fetching events:", error);
// could just load from a backup file instead
console.error("Error fetching events on startup:", error);
}

const app: Express = express();
const port = process.env.PORT || 9000;

// Middleware
app.use(
express.json({
Expand All@@ -29,11 +31,11 @@ dotenv.config();
})
);
app.use(cors());

app.use(pingRoute);
app.use(eventsWebhookRoute);
app.use(eventsRoute);

app.listen(port, () => {
console.log(`Server successfully started on port ${port}`);
});
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp