Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ArmaEvents API for event display #45

Merged
merged 1 commit into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
144 changes: 89 additions & 55 deletions api/src/utils/EventsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,67 @@ export interface ArmaEvent {
attendance: [number, number]
}

const QUERY = `
query GetCommunityEventsWithDetails($communityId: UUID!) {
events(
where: { community: { id: { eq: $communityId } } }
order: [
{ date: DESC } # Sort by date in descending order
]
) {
nodes {
id
title
slug
community {
slug
}
date
attendingUsers: eventUsers(where: { status: { eq: attending } }) {
totalCount
}
potentiallyAttendingUsers: eventUsers(
where: { status: { eq: potentiallyAttending } }
) {
totalCount
}
}
}
}
`;

interface GetCommunityEventsWithDetailsQuery {
__typename?: 'Query'
events?: {
__typename?: 'EventsConnection'
nodes?: Array<{
__typename?: 'Event'
id: string
title: string
slug: string
date?: string | null
community: {
slug: string
}
attendingUsers?: {
__typename?: 'EventUsersConnection'
totalCount: number
} | null
potentiallyAttendingUsers?: {
__typename?: 'EventUsersConnection'
totalCount: number
} | null
}> | null
} | null
};

interface GetCommunityEventsWithDetailsQueryVariables {
communityId: string
};

export class ArmaEventsService {
private static readonly ATTENDANCE_PLUGIN_INTRODUCTION = 1483833600000;
private static readonly FORUM_URI = 'https://forum.gruppe-adler.de';
private static readonly TOPIC_TITLE_REGEX = /^\d{4}-\d{2}-\d{2}\s*,(\s*\w+\s*,)?\s*/i;
private static readonly ARMA_EVENTS_API_URL = 'https://api.arma.events/graphql/';
private static readonly ARMA_EVENTS_GRUPPE_ADLER_ID = 'f52e5ce0-928d-45a9-9381-6f93015950ee';

private static instance: ArmaEventsService | null = null;

Expand Down Expand Up @@ -49,43 +106,47 @@ export class ArmaEventsService {
}

private async fetchEvents (): Promise<ArmaEvent[]> {
const response = await fetch(`${ArmaEventsService.FORUM_URI}/api/events/cid-3`);
const variables: GetCommunityEventsWithDetailsQueryVariables = { communityId: ArmaEventsService.ARMA_EVENTS_GRUPPE_ADLER_ID };
const response = await fetch(ArmaEventsService.ARMA_EVENTS_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query: QUERY, variables })
});

if (!response.ok) {
throw new Error(`Error while trying to fetch events. Forum API responded with status code ${response.status}.`);
throw new Error(`Error fetching data from arma.events! Status: ${response.status}`);
}
const { topics } = await response.json() as { topics: Array<{ slug: string, titleRaw: string, tid: number, deleted: 1 | 0, timestamp: number }> };

const rawEvents: Array<Omit<ArmaEvent, 'attendance'> & { tid: number }> = [];
for (const topic of topics) {
if (topic.timestamp < ArmaEventsService.ATTENDANCE_PLUGIN_INTRODUCTION) continue;
if (topic.deleted === 1) {
console.warn(`Skipping topic ${topic.titleRaw} (ID: ${topic.tid}), because it was deleted`);
continue;
}
if (!ArmaEventsService.TOPIC_TITLE_REGEX.test(topic.titleRaw)) {
console.warn(`Skipping topic ${topic.titleRaw} (ID: ${topic.tid}), because its title did not match the required pattern.`);
continue;
}

rawEvents.push({
date: new Date(topic.titleRaw.substr(0, 10)),
title: topic.titleRaw.replace(ArmaEventsService.TOPIC_TITLE_REGEX, ''),
url: `${ArmaEventsService.FORUM_URI}/topic/${topic.slug}`,
tid: topic.tid
});
const result = await response.json() as {
data: GetCommunityEventsWithDetailsQuery
errors?: any
};

if (result.errors) {
console.error(result.errors);
throw new Error(`arma.events GraphQL error occurred! Error: ${JSON.stringify(result.errors)}`);
}

// events sorted with event furthest back in past as first item
// Transform the data to fit ArmaEvent interface
const rawEvents = result.data.events?.nodes?.map((event): ArmaEvent => {
return {
date: new Date(event.date ?? 0),
title: event.title,
url: `https://arma.events/e/${event.community.slug}/${event.slug}`, // Construct URL using slug
attendance: [event.attendingUsers?.totalCount ?? 0, event.potentiallyAttendingUsers?.totalCount ?? 0]
};
}) ?? [];

const sortedEvents = rawEvents.sort((a, b) => a.date.getTime() - b.date.getTime());

// we only want one future event and a max of 10 events;
// if there is no future event we just want the 10 most recent events
const firstFutureEvent = sortedEvents.findIndex(e => ArmaEventsService.isInFuture(e.date)) + 1;
const filteredEvents = sortedEvents.slice(0, firstFutureEvent > 0 ? firstFutureEvent : sortedEvents.length).reverse().slice(0, 10);

const promises = filteredEvents.map(async ({ tid, ...rest }) => await ArmaEventsService.fetchAttendance(tid).then((attendance): ArmaEvent => ({ ...rest, attendance })));

return await Promise.all(promises);
return filteredEvents;
}

/**
Expand All @@ -106,31 +167,4 @@ export class ArmaEventsService {

return dateCopy.getTime() > (new Date()).getTime();
}

/**
* Fetch attendance for given topic id
* @param {number} tid Topic id
* @returns {[number, number]} [Number of firm commitments, Number of "maybes"]
*/
private static async fetchAttendance (tid: number): Promise<[number, number]> {
const response = await fetch(`${ArmaEventsService.FORUM_URI}/api/attendance/${tid}`);

if (!response.ok) {
throw new Error(`Error while trying to fetch attendance for topic ${tid}. Forum API responded with status code ${response.status}.`);
}
const { attendants } = await response.json() as { attendants: Array<{ probability: 0 | 0.5 | 1 }> };

let firm = 0;
let maybe = 0;

for (const { probability } of attendants) {
if (probability === 0.5) {
maybe++;
} else if (probability === 1) {
firm++;
}
}

return [firm, maybe];
}
}
4 changes: 2 additions & 2 deletions frontend/src/components/Home/Events.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@
<a
v-else-if="expanded || small"
class="grad-arma-events__more"
href="https://forum.gruppe-adler.de/category/3"
href="https://arma.events/c/gruppe-adler/events"
target="_blank"
rel="noreferrer"
>
Mehr Events im Forum
Mehr Events auf arma.events
</a>
<span v-else class="grad-arma-events__more" @click="expanded = true" tabindex="0" role="button">Mehr anzeigen</span>
</Container>
Expand Down
Loading