Skip to content

Commit 3b897e1

Browse files
committed
Add guild
1 parent 9c4aeae commit 3b897e1

File tree

11 files changed

+209
-60
lines changed

11 files changed

+209
-60
lines changed

packages/service/src/collector.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ComposedService, type ContentData, type ServiceMetadata } from "./services/abstract.js";
22
import { ArticleService } from "./services/article-service.js";
3+
import { GuildService } from "./services/guild-service.js";
34
import { PackageService } from "./services/package-service.js";
45
import { type ConnectedService, SocietyStatsService } from "./services/society-stats-service.js";
56
import { VideoService } from "./services/video-service.js";
@@ -8,7 +9,7 @@ import { VideoService } from "./services/video-service.js";
89
export class CollectorService extends ComposedService<any> {
910
private readonly stats = new SocietyStatsService();
1011
constructor(private userService: ConnectedService) {
11-
super([new VideoService(), new PackageService(), new ArticleService()]);
12+
super([new VideoService(), new PackageService(), new ArticleService(), new GuildService()]);
1213
}
1314

1415
async getInformation(metadata: ServiceMetadata): Promise<ContentData & StatsData & Record<string, unknown>> {

packages/service/src/components/content.svelte

+18-12
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<script lang="ts">
22
// biome-ignore lint/correctness/noUnusedImports: Used in Markup
33
import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
4+
// biome-ignore lint/correctness/noUnusedImports: Used in Markup
5+
import SvelteMarkdown from "svelte-markdown";
46
57
export let type: string;
68
export let author: string;
7-
export let lastUpdate: string;
9+
export let lastUpdate: string | undefined;
810
export let views: number;
911
export let liked = false;
1012
export let likes: number;
@@ -20,8 +22,8 @@ export let connected = false;
2022
<div>
2123
<strong>{type}</strong>
2224
posted by {author}
23-
&bull;
24-
{formatDistanceToNow(lastUpdate ?? (new Date().toISOString()))}
25+
{#if lastUpdate}&bull;
26+
{formatDistanceToNow(lastUpdate ?? (new Date().toISOString()))}{/if}
2527
&bull;
2628
<span>{views}
2729
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -51,17 +53,21 @@ export let connected = false;
5153
{/if}
5254
</header>
5355
<h3>{name}</h3>
54-
<p>{description}</p>
56+
<SvelteMarkdown source={description} />
5557
<section><slot></slot></section>
5658
<footer>
57-
<ul>
58-
{#each tags as tag}
59-
<li>#️ {tag}</li>
60-
{/each}
61-
</ul>
62-
<aside>
63-
{formatDistanceToNow(lastUpdate ?? Date.now())}
64-
</aside>
59+
{#if tags.length}
60+
<ul>
61+
{#each tags as tag}
62+
<li>#️ {tag}</li>
63+
{/each}
64+
</ul>
65+
{/if}
66+
{#if lastUpdate}
67+
<aside>
68+
{formatDistanceToNow(lastUpdate ?? Date.now())}
69+
</aside>
70+
{/if}
6571
</footer>
6672
</article>
6773

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<script lang="ts">
2+
// biome-ignore lint/correctness/noUnusedImports: Used in Markup
3+
import Content from "./content.svelte";
4+
export let author: string;
5+
export let lastUpdate: string;
6+
export let views: number;
7+
export let liked = false;
8+
export let likes: number;
9+
export let saved = false;
10+
export let name: string;
11+
export let cover: string;
12+
export let members: number;
13+
export let description: string;
14+
export let tags: Array<string> = [];
15+
export let connected = false;
16+
</script>
17+
18+
<Content {...$$props} type="Guild">
19+
{#if cover}
20+
<figure>
21+
<img src={cover}/>
22+
</figure>
23+
{/if}
24+
<strong>{members}</strong> Members
25+
</Content>
26+
27+
<style>
28+
figure {
29+
margin: 0;
30+
padding: 0;
31+
}
32+
33+
figure img {
34+
width: 600px;
35+
height: 300px;
36+
37+
border-radius: 8px;
38+
}
39+
</style>

packages/service/src/components/package.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export let tags: Array<string> = [];
1515
export let connected = false;
1616
</script>
1717

18-
<Content {...$$props}>
18+
<Content {...$$props} type="Package">
1919
{#if cover}
2020
<figure>
2121
<img src={cover}/>

packages/service/src/components/recipe.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export let tags: Array<string> = [];
1616
export let connected = false;
1717
</script>
1818

19-
<Content type="Recipe" {...$$props}>
19+
<Content {...$$props} type="Recipe">
2020
<SvelteMarkdown source={preview} />
2121
<br />
2222
<a href="">Read more...</a>

packages/service/src/components/video.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export let tags: Array<string> = [];
1414
export let connected = false;
1515
</script>
1616

17-
<Content type="Video" {...$$props}>
17+
<Content {...$$props} type="Video">
1818
<iframe id="player" width="600" height="340"
1919
src={embed}
2020
style="border:none"></iframe>

packages/service/src/services/abstract.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export type ContentData = {
77
type: string;
88
name: string;
99
author: string;
10-
lastUpdate: string;
10+
lastUpdate?: string;
1111
keywords: Array<string>;
1212
description: string;
1313
};

packages/service/src/services/article-service.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Memorize, longTermCache } from "../cache.js";
2-
import {ComposedService, type ContentData, type ServiceInterface, type ServiceMetadata} from "./abstract.js";
2+
import { ComposedService, type ContentData, type ServiceInterface, type ServiceMetadata } from "./abstract.js";
33

44
export const TYPE = "article";
55
export const RECIPE_TYPE = "recipe";
@@ -10,7 +10,6 @@ export class ArticleService extends ComposedService<{ preview: string }> {
1010
}
1111
}
1212

13-
1413
export class RecipeService implements ServiceInterface<{ preview: string }> {
1514
canHandle(metadata: ServiceMetadata): Promise<boolean> {
1615
return Promise.resolve([RECIPE_TYPE, TYPE].includes(metadata.type));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { Memorize, longTermCache } from "../cache.js";
2+
import type { ContentData, ServiceInterface, ServiceMetadata } from "./abstract.js";
3+
4+
export const TYPE = "guild" as const;
5+
6+
export class GuildService implements ServiceInterface<{ cover: string; members: number }> {
7+
canHandle(metadata: ServiceMetadata): Promise<boolean> {
8+
return Promise.resolve(metadata.type === TYPE);
9+
}
10+
async getInformation(metadata: ServiceMetadata): Promise<(ContentData & { cover: string; members: number }) | never> {
11+
const guilds = await this.getGuilds();
12+
let guild: GuildHostGuildNodeResponse | undefined = guilds.data.nodeBySlugId.guilds.edges.find(
13+
(guild) => guild.node.slugId === metadata.identifier,
14+
)?.node;
15+
16+
if (guild === undefined) {
17+
guild = (await this.getOneGuild(metadata.identifier))?.data.nodeBySlugId;
18+
}
19+
20+
if (guild === undefined) {
21+
throw new Error("Invalid data service");
22+
}
23+
24+
return {
25+
name: guild.name,
26+
description: guild.description,
27+
type: TYPE,
28+
author: "guild.host",
29+
keywords: [],
30+
members: guild.networkMembers.totalCount,
31+
cover: guild.backgroundPhoto
32+
? `https://ik.imagekit.io/guild/prod/tr:w-600,dpr-1/${guild.backgroundPhoto?.rowId}.${guild.backgroundPhoto?.contentType.toLowerCase()}`
33+
: "",
34+
};
35+
}
36+
37+
@Memorize(longTermCache)
38+
private getOneGuild(id: string): Promise<GuildHostOneResponse> {
39+
return fetch("https://guild.host/graphql/1bab259132f26a669a0dbe5184260401be37f259bf888010711abab5438e8e51", {
40+
headers: {
41+
"X-Gqlvars": `{"id":"${id}"}`,
42+
},
43+
}).then((response) => response.json());
44+
}
45+
46+
@Memorize(longTermCache)
47+
private getGuilds(): Promise<GuildHostResponse> {
48+
return fetch("https://guild.host/graphql/932d20dc08db4fb8d9f86e17908ba8ccb8b1de931ad51718689570c942c82c71", {
49+
headers: {
50+
"X-Gqlvars": '{"id":"svelte-society"}',
51+
},
52+
}).then((response) => response.json());
53+
}
54+
55+
getAllServiceMetadata(): Promise<Array<ServiceMetadata>> {
56+
return this.getGuilds().then((response) =>
57+
response.data.nodeBySlugId.guilds.edges.map((guild) => ({
58+
type: TYPE,
59+
identifier: guild.node.slugId,
60+
})),
61+
);
62+
}
63+
}
64+
65+
type GuildHostResponse = {
66+
data: {
67+
nodeBySlugId: {
68+
__typename: "Guild";
69+
name: string;
70+
slugId: string;
71+
description: string;
72+
networks: { edges: []; pageInfo: { endCursor: null; hasNextPage: boolean } };
73+
id: string;
74+
guilds: {
75+
edges: Array<{
76+
node: GuildHostGuildNodeResponse;
77+
cursor: string;
78+
}>;
79+
pageInfo: { endCursor: string; hasNextPage: boolean };
80+
};
81+
__isNode: "Guild";
82+
};
83+
};
84+
};
85+
86+
type GuildHostGuildNodeResponse = {
87+
__typename: "Guild";
88+
id: string;
89+
slugId: string;
90+
type: "NETWORK" | "GUILD";
91+
myMembership: unknown;
92+
minimumDonationAmount?: number;
93+
donationCurrency?: "EUR";
94+
rowId: string;
95+
name: string;
96+
description: string;
97+
networkMembers: {
98+
totalCount: number;
99+
};
100+
primaryPhoto: {
101+
rowId: string;
102+
contentType: "PNG" | "WEBP" | "JPEG";
103+
id: string;
104+
} | null;
105+
backgroundPhoto: {
106+
rowId: string;
107+
contentType: "PNG" | "WEBP" | "JPEG";
108+
id: string;
109+
} | null;
110+
totalNetworks?: {
111+
edges: Array<unknown>;
112+
};
113+
totalGuilds?: {
114+
edges: Array<{
115+
cursor: string;
116+
}>;
117+
};
118+
totalEvents?: {
119+
edges: Array<{
120+
cursor: string;
121+
}>;
122+
};
123+
totalPresentations?: {
124+
edges: Array<{
125+
cursor: string;
126+
}>;
127+
};
128+
__isNode?: "Guild";
129+
};
130+
131+
type GuildHostOneResponse = {
132+
data: {
133+
nodeBySlugId: GuildHostGuildNodeResponse;
134+
};
135+
};

sites/www/src/routes/(app)/+page.server.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CollectorService } from 'sveltesociety.dev-service/src/collector.js';
2+
import { GuildService } from 'sveltesociety.dev-service/src/services/guild-service';
23
import { ConnectedService } from 'sveltesociety.dev-service/src/services/society-stats-service.js';
34
import type { PageServerLoad } from './$types';
45

@@ -15,7 +16,9 @@ export const load: PageServerLoad = async () => {
1516
{ type: 'video', identifier: '330781388' },
1617
{ type: 'recipe', identifier: 'xxx' },
1718
{ type: 'package', identifier: 'svelte' },
18-
{ type: 'package', identifier: 'svelte-atoms' }
19+
{ type: 'package', identifier: 'svelte-atoms' },
20+
{ type: 'guild', identifier: 'london-javascript' },
21+
...(await new GuildService().getAllServiceMetadata())
1922
])
2023
};
2124
};

sites/www/src/routes/(app)/+page.svelte

+6-40
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import Package from 'sveltesociety.dev-service/src/components/package.svelte';
33
import Recipe from 'sveltesociety.dev-service/src/components/recipe.svelte';
44
import Video from 'sveltesociety.dev-service/src/components/video.svelte';
5+
import Guild from 'sveltesociety.dev-service/src/components/guild.svelte';
56
67
import type { PageData } from './$types';
78
export let data: PageData;
@@ -14,48 +15,13 @@ Home page!
1415
<main>
1516
{#each data.items as item}
1617
{#if item.type === 'package'}
17-
<Package
18-
type="Library"
19-
description={item.description}
20-
name={item.name}
21-
author={item.author}
22-
lastUpdate={item.lastUpdate}
23-
likes={item.likes}
24-
tags={item.keywords}
25-
cover={item.cover}
26-
connected={item.connected}
27-
views={item.views}
28-
liked={item.liked}
29-
saved={item.saved}
30-
/>
18+
<Package {...item} tags={item.keywords} />
3119
{:else if item.type === 'video'}
32-
<Video
33-
description={item.description}
34-
name={item.name}
35-
author={item.author}
36-
lastUpdate={item.lastUpdate}
37-
likes={item.likes}
38-
tags={item.keywords}
39-
embed={item.embed}
40-
connected={item.connected}
41-
views={item.views}
42-
liked={item.liked}
43-
saved={item.saved}
44-
/>
20+
<Video {...item} tags={item.keywords} />
4521
{:else if item.type === 'recipe'}
46-
<Recipe
47-
description={item.description}
48-
name={item.name}
49-
author={item.author}
50-
lastUpdate={item.lastUpdate}
51-
likes={item.likes}
52-
tags={item.keywords}
53-
preview={item.preview}
54-
connected={item.connected}
55-
views={item.views}
56-
liked={item.liked}
57-
saved={item.saved}
58-
/>
22+
<Recipe {...item} tags={item.keywords} />
23+
{:else if item.type === 'guild'}
24+
<Guild {...item} />
5925
{/if}
6026
{/each}
6127
</main>

0 commit comments

Comments
 (0)