Skip to content

Commit 55f6906

Browse files
committed
GET /w/contributions-sitemap.xml
1 parent 71b4908 commit 55f6906

File tree

5 files changed

+84
-0
lines changed

5 files changed

+84
-0
lines changed

api/src/app/endpoints.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
GetContributionResponse,
3+
GetContributionsForSitemapResponse,
34
GetContributionsResponse,
45
GetContributionTitleResponse,
56
} from "src/contribution/types";
@@ -46,6 +47,9 @@ export interface Endpoints {
4647
response: GetContributionTitleResponse;
4748
params: { id: string };
4849
};
50+
"api:contributions/for-sitemap": {
51+
response: GetContributionsForSitemapResponse;
52+
};
4953
"api:Contributors": {
5054
response: GetContributorsResponse;
5155
};

api/src/contribution/controller.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
GetContributionTitleResponse,
77
GetContributionResponse,
88
GetContributionsResponse,
9+
GetContributionsForSitemapResponse,
910
} from "./types";
1011

1112
@Service()
@@ -22,6 +23,16 @@ export class ContributionController {
2223
};
2324
}
2425

26+
@Get("/for-sitemap")
27+
public async getContributionsForSitemap(): Promise<GetContributionsForSitemapResponse> {
28+
// @TODO-ZM: title is a markdown, we should render it to plain text
29+
const contributions = await this.contributionRepository.findForSitemap();
30+
31+
return {
32+
contributions,
33+
};
34+
}
35+
2536
@Get("/:id")
2637
public async getContribution(@Param("id") id: string): Promise<GetContributionResponse> {
2738
const contribution = await this.contributionRepository.findByIdWithStats(id);

api/src/contribution/repository.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ export class ContributionRepository {
7979
return camelCased;
8080
}
8181

82+
public async findForSitemap() {
83+
const statement = sql`
84+
SELECT
85+
${contributionsTable.id},
86+
${contributionsTable.title}
87+
FROM
88+
${contributionsTable}
89+
`;
90+
91+
const raw = await this.postgresService.db.execute(statement);
92+
const entries = Array.from(raw);
93+
const unStringifiedRaw = unStringifyDeep(entries);
94+
const camelCased = camelCaseObject(unStringifiedRaw);
95+
return camelCased;
96+
}
97+
8298
public async upsert(contribution: ContributionRow) {
8399
return await this.postgresService.db
84100
.insert(contributionsTable)

api/src/contribution/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,7 @@ export interface GetContributionResponse extends GeneralResponse {
3030
export interface GetContributionTitleResponse extends GeneralResponse {
3131
contribution: Pick<ContributionEntity, "title">;
3232
}
33+
34+
export interface GetContributionsForSitemapResponse extends GeneralResponse {
35+
contributions: Array<Pick<ContributionEntity, "id" | "title">>;
36+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Env } from "handler/contribution";
2+
import { environments } from "@dzcode.io/utils/dist/config/environment";
3+
import { allLanguages, LanguageEntity } from "@dzcode.io/models/dist/language";
4+
import { getContributionURL } from "@dzcode.io/web/dist/utils/contribution";
5+
import { fsConfig } from "@dzcode.io/utils/dist/config";
6+
import { fetchV2Factory } from "@dzcode.io/utils/dist/fetch/factory";
7+
import { Endpoints } from "@dzcode.io/api/dist/app/endpoints";
8+
9+
export const onRequest: PagesFunction<Env> = async (context) => {
10+
let stage = context.env.STAGE;
11+
if (!environments.includes(stage)) {
12+
console.log(`⚠️ No STAGE provided, falling back to "development"`);
13+
stage = "development";
14+
}
15+
const fullstackConfig = fsConfig(stage);
16+
const fetchV2 = fetchV2Factory<Endpoints>(fullstackConfig);
17+
18+
const { contributions } = await fetchV2("api:contributions/for-sitemap", {});
19+
20+
const hostname = "https://www.dzCode.io";
21+
const links = contributions.reduce<{ url: string; lang: LanguageEntity["code"] }[]>((pV, cV) => {
22+
return [
23+
...pV,
24+
...allLanguages.map(({ baseUrl, code }) => ({
25+
url: `${baseUrl}${getContributionURL(cV)}`,
26+
lang: code,
27+
})),
28+
];
29+
}, []);
30+
31+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
32+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
33+
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
34+
xmlns:xhtml="http://www.w3.org/1999/xhtml"
35+
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
36+
xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
37+
${links
38+
.map(
39+
(link) => `
40+
<url>
41+
<loc>${hostname}${link.url}</loc>
42+
<xhtml:link rel="alternate" hreflang="${link.lang}" href="${hostname}${link.url}" />
43+
</url>`,
44+
)
45+
.join("")}
46+
</urlset>`;
47+
48+
return new Response(xml, { headers: { "content-type": "application/xml; charset=utf-8" } });
49+
};

0 commit comments

Comments
 (0)