diff --git a/packages/service/src/collector.ts b/packages/service/src/collector.ts index 0ea0c81b2..cd121ce0e 100644 --- a/packages/service/src/collector.ts +++ b/packages/service/src/collector.ts @@ -4,12 +4,13 @@ import { GuildService } from "./services/guild-service.js"; import { PackageService } from "./services/package-service.js"; import { type ConnectedService, SocietyStatsService } from "./services/society-stats-service.js"; import { VideoService } from "./services/video-service.js"; +import {GuildEventService} from "./services/guild-event-service.js"; // biome-ignore lint/suspicious/noExplicitAny: export class CollectorService extends ComposedService { private readonly stats = new SocietyStatsService(); constructor(private userService: ConnectedService) { - super([new VideoService(), new PackageService(), new ArticleService(), new GuildService()]); + super([new VideoService(), new PackageService(), new ArticleService(), new GuildService(), new GuildEventService()]); } async getInformation(metadata: ServiceMetadata): Promise> { diff --git a/packages/service/src/services/guild-event-service.ts b/packages/service/src/services/guild-event-service.ts new file mode 100644 index 000000000..d835fe829 --- /dev/null +++ b/packages/service/src/services/guild-event-service.ts @@ -0,0 +1,110 @@ +import {Memorize, shortTermCache} from "../cache.js"; +import type { ContentData, ServiceInterface, ServiceMetadata } from "./abstract.js"; + +export const TYPE = "guild-event" as const; + +export class GuildEventService implements ServiceInterface<{ cover: string, startAt: string, venue?: [number, number] }> { + canHandle(metadata: ServiceMetadata): Promise { + return Promise.resolve(metadata.type === TYPE); + } + async getInformation(metadata: ServiceMetadata): Promise<(ContentData & { cover: string, startAt: string, venue?: [number, number] }) | never> { + const event = (await this.getGuildEvents('svelte-society')).events.edges.find(event => event.node.id === metadata.identifier) + + if (event) { + return { + type: TYPE, + cover: event.node.uploadedSocialCard?.url ?? event.node.generatedSocialCardURL, + keywords: [], + url: `http://guild.host/events/${event.node.prettyUrl}`, + author: event.node.owner.name, + venue: event.node.hasVenue && event.node.venue?.address.location !== null ? event.node.venue?.address.location.geojson.coordinates : undefined, + description: event.node.description, + name: event.node.name, + startAt: event.node.startAt + } + } + + return this.getOneEvent(metadata.identifier).then(response => ({ + type: TYPE, + cover: response.uploadedSocialCard?.url ?? response.generatedSocialCardURL, + keywords: [], + url: `http://guild.host/events/${response.prettyUrl}`, + author: response.owner.name, + venue: response.hasVenue && response.venue?.address.location !== null ? response.venue?.address.location.geojson.coordinates : undefined, + description: response.description, + name: response.name, + startAt: response.startAt + })) + } + + @Memorize(shortTermCache) + private getGuildEvents(id: string): Promise { + return fetch(`https://guild.host/api/next/${id}/events`).then(response => response.json()) + } + + @Memorize(shortTermCache) + private getOneEvent(id: string): Promise { + return fetch(`https://guild.host/api/next/node/${id}`).then(response => response.json()) + } + + async getAllServiceMetadata(): Promise> { + return this.getGuildEvents('svelte-society').then(response => response.events.edges.map(event => ({ + type: TYPE, + identifier: event.node.id + }))) + } +} + +type GuildHostGuildEventsResponse = { + "__typename": "Guild", "events": { + "edges": Array<{ + "node": GuildHostEventResponse, + "cursor": string + }>, + "pageInfo": { + "hasPreviousPage": boolean, + "hasNextPage": boolean, + "startCursor": string, + "endCursor": string + } + }, "__isNode": "Guild", "id": string +}; + +type GuildHostEventResponse = { + "__typename"?: "Event", + "id": string, + "slug": string, + "prettyUrl": string, + "name": string, + "description": string, + "startAt": string, + "endAt": string, + "timeZone": string, + "visibility": "PUBLIC", + "hasVenue": boolean, + "hasExternalUrl": boolean, + "owner": { + "__typename": "Guild", + "id": string, + "name": string, + "__isNode": "Guild" + }, + "uploadedSocialCard": null | { + "url": string, + "id": string + }, + "generatedSocialCardURL": string, + "presentations": { "edges": [] }, + "venue": null | { + "address": { + "location": null | { + "__typename": "GeometryPoint", + "geojson": { "type": "Point", "coordinates": [number, number] } + }, + "id": string + }, + "id": string + }, + "createdAt": string, + "updatedAt": string +} diff --git a/sites/www/src/lib/ui/ContentCard.svelte b/sites/www/src/lib/ui/ContentCard.svelte index 54bd4da74..f184acd91 100644 --- a/sites/www/src/lib/ui/ContentCard.svelte +++ b/sites/www/src/lib/ui/ContentCard.svelte @@ -1,7 +1,7 @@ @@ -10,7 +10,11 @@
{type}  by {author} •{#if time} {formatDistanceToNow(time)} •{/if}  + >by {author} •{#if time} {formatDistanceToNow(time, { + addSuffix: true + })} •{/if}  {views} @@ -60,7 +64,7 @@
-

{title}

+

{title}

{@render children()} @@ -70,6 +74,8 @@ - {#if time}
 {formatDistanceToNow(time)}
{/if} + {#if time}
+  {formatDistanceToNow(time, { addSuffix: true })} +
{/if} diff --git a/sites/www/src/lib/ui/cards/GuildEventCard.svelte b/sites/www/src/lib/ui/cards/GuildEventCard.svelte new file mode 100644 index 000000000..e90d36fa0 --- /dev/null +++ b/sites/www/src/lib/ui/cards/GuildEventCard.svelte @@ -0,0 +1,62 @@ + + + + {#if cover} +
+ {title} +
+ {/if} + {#if Array.isArray(venue)} +
+ +
+ {/if} +
+ + diff --git a/sites/www/src/routes/(app)/+page.server.ts b/sites/www/src/routes/(app)/+page.server.ts index 9016dd67b..dc3807c86 100644 --- a/sites/www/src/routes/(app)/+page.server.ts +++ b/sites/www/src/routes/(app)/+page.server.ts @@ -2,6 +2,7 @@ import { CollectorService } from 'sveltesociety.dev-service/src/collector.js'; import { GuildService } from 'sveltesociety.dev-service/src/services/guild-service'; import { ConnectedService } from 'sveltesociety.dev-service/src/services/society-stats-service.js'; import type { PageServerLoad } from './$types'; +import { GuildEventService } from 'sveltesociety.dev-service/src/services/guild-event-service'; export const load: PageServerLoad = async () => { // TODO: get user info from request (cookie?) @@ -18,7 +19,8 @@ export const load: PageServerLoad = async () => { { type: 'package', identifier: 'svelte' }, { type: 'package', identifier: 'svelte-atoms' }, { type: 'guild', identifier: 'london-javascript' }, - ...(await new GuildService().getAllServiceMetadata()) + ...(await new GuildService().getAllServiceMetadata()), + ...(await new GuildEventService().getAllServiceMetadata()) ]) }; }; diff --git a/sites/www/src/routes/(app)/+page.svelte b/sites/www/src/routes/(app)/+page.svelte index c5479fa25..e57047312 100644 --- a/sites/www/src/routes/(app)/+page.svelte +++ b/sites/www/src/routes/(app)/+page.svelte @@ -6,6 +6,7 @@ import PackageCard from '$lib/ui/cards/PackageCard.svelte'; import RecipeCard from '$lib/ui/cards/RecipeCard.svelte'; import VideoCard from '$lib/ui/cards/VideoCard.svelte'; + import GuildEventCard from '$lib/ui/cards/GuildEventCard.svelte'; export let data: PageData; let tags = [ @@ -52,5 +53,7 @@ /> {:else if item.type === 'guild'} + {:else if item.type === 'guild-event'} + {/if} {/each}