-
Notifications
You must be signed in to change notification settings - Fork 45
Feat: contribution page #621
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
Changes from 7 commits
5563024
7f48d22
a5c73d3
a12a8cd
71b4908
55f6906
8f603bd
de7f69a
fcc1eb5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,3 +14,23 @@ export interface GetContributionsResponse extends GeneralResponse { | |
} | ||
>; | ||
} | ||
|
||
export interface GetContributionResponse extends GeneralResponse { | ||
contribution: Pick< | ||
ContributionEntity, | ||
"id" | "title" | "type" | "url" | "updatedAt" | "activityCount" | ||
> & { | ||
repository: Pick<RepositoryEntity, "id" | "owner" | "name"> & { | ||
project: Pick<ProjectEntity, "id" | "name">; | ||
}; | ||
contributor: Pick<ContributorEntity, "id" | "name" | "username" | "avatarUrl">; | ||
Comment on lines
+19
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should improve these types so it can be easy to be shared There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. any particular action item for this PR? or this is a general point that we should address at some point? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say just a general note for later, like allowing type aliases for readability |
||
}; | ||
} | ||
|
||
export interface GetContributionTitleResponse extends GeneralResponse { | ||
contribution: Pick<ContributionEntity, "title">; | ||
} | ||
|
||
export interface GetContributionsForSitemapResponse extends GeneralResponse { | ||
contributions: Array<Pick<ContributionEntity, "id" | "title">>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { Env, handleContributionRequest } from "handler/contribution"; | ||
|
||
export const onRequest: PagesFunction<Env> = handleContributionRequest; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { Env, handleContributionRequest } from "handler/contribution"; | ||
|
||
export const onRequest: PagesFunction<Env> = handleContributionRequest; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { Env } from "handler/contribution"; | ||
import { environments } from "@dzcode.io/utils/dist/config/environment"; | ||
import { allLanguages, LanguageEntity } from "@dzcode.io/models/dist/language"; | ||
import { getContributionURL } from "@dzcode.io/web/dist/utils/contribution"; | ||
import { fsConfig } from "@dzcode.io/utils/dist/config"; | ||
import { fetchV2Factory } from "@dzcode.io/utils/dist/fetch/factory"; | ||
import { Endpoints } from "@dzcode.io/api/dist/app/endpoints"; | ||
|
||
function xmlEscape(s: string) { | ||
return s.replace( | ||
/[<>&"']/g, | ||
(c) => ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[c] as string, | ||
); | ||
} | ||
|
||
export const onRequest: PagesFunction<Env> = async (context) => { | ||
let stage = context.env.STAGE; | ||
if (!environments.includes(stage)) { | ||
console.log(`⚠️ No STAGE provided, falling back to "development"`); | ||
stage = "development"; | ||
} | ||
const fullstackConfig = fsConfig(stage); | ||
const fetchV2 = fetchV2Factory<Endpoints>(fullstackConfig); | ||
|
||
const { contributions } = await fetchV2("api:contributions/for-sitemap", {}); | ||
|
||
const hostname = "https://www.dzCode.io"; | ||
const links = contributions.reduce<{ url: string; lang: LanguageEntity["code"] }[]>((pV, cV) => { | ||
return [ | ||
...pV, | ||
...allLanguages.map(({ baseUrl, code }) => ({ | ||
url: xmlEscape(`${baseUrl}${getContributionURL(cV)}`), | ||
lang: code, | ||
})), | ||
]; | ||
}, []); | ||
|
||
const xml = `<?xml version="1.0" encoding="UTF-8"?> | ||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" | ||
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" | ||
xmlns:xhtml="http://www.w3.org/1999/xhtml" | ||
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" | ||
xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"> | ||
${links | ||
.map( | ||
(link) => ` | ||
<url> | ||
<loc>${hostname}${link.url}</loc> | ||
<xhtml:link rel="alternate" hreflang="${link.lang}" href="${hostname}${link.url}" /> | ||
</url>`, | ||
) | ||
.join("")} | ||
</urlset>`; | ||
|
||
return new Response(xml, { headers: { "content-type": "application/xml; charset=utf-8" } }); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
declare const htmlTemplate: string; // @ts-expect-error cloudflare converts this to a string using esbuild | ||
import htmlTemplate from "../public/template.html"; | ||
declare const notFoundEn: string; // @ts-expect-error cloudflare converts this to a string using esbuild | ||
import notFoundEn from "../public/404.html"; | ||
declare const notFoundAr: string; // @ts-expect-error cloudflare converts this to a string using esbuild | ||
import notFoundAr from "../public/ar/404.html"; | ||
|
||
import { Environment, environments } from "@dzcode.io/utils/dist/config/environment"; | ||
import { fsConfig } from "@dzcode.io/utils/dist/config"; | ||
import { plainLocalize } from "@dzcode.io/web/dist/components/locale/utils"; | ||
import { dictionary, AllDictionaryKeys } from "@dzcode.io/web/dist/components/locale/dictionary"; | ||
import { LanguageEntity } from "@dzcode.io/models/dist/language"; | ||
import { fetchV2Factory } from "@dzcode.io/utils/dist/fetch/factory"; | ||
import { Endpoints } from "@dzcode.io/api/dist/app/endpoints"; | ||
|
||
export interface Env { | ||
STAGE: Environment; | ||
} | ||
|
||
export const handleContributionRequest: PagesFunction<Env> = async (context) => { | ||
let stage = context.env.STAGE; | ||
if (!environments.includes(stage)) { | ||
console.log(`⚠️ No STAGE provided, falling back to "development"`); | ||
stage = "development"; | ||
} | ||
|
||
const pathName = new URL(context.request.url).pathname; | ||
|
||
const languageRegex = /^\/(ar|en)\//i; | ||
const language = (pathName?.match(languageRegex)?.[1]?.toLowerCase() || | ||
"en") as LanguageEntity["code"]; | ||
const notFound = language === "ar" ? notFoundAr : notFoundEn; | ||
|
||
const contributionIdRegex = /contribute\/(.*)-(.*)-(.*)/; | ||
const contributionId = | ||
pathName?.match(contributionIdRegex)?.[2] + "-" + pathName?.match(contributionIdRegex)?.[3]; | ||
|
||
if (!contributionId) | ||
return new Response(notFound, { | ||
headers: { "content-type": "text/html; charset=utf-8" }, | ||
status: 404, | ||
}); | ||
|
||
const localize = (key: AllDictionaryKeys) => | ||
plainLocalize(dictionary, language, key, "NO-TRANSLATION"); | ||
|
||
const fullstackConfig = fsConfig(stage); | ||
const fetchV2 = fetchV2Factory<Endpoints>(fullstackConfig); | ||
|
||
try { | ||
const { contribution } = await fetchV2("api:contributions/:id/title", { | ||
params: { id: contributionId }, | ||
}); | ||
const pageTitle = `${localize("contribution-title-pre")} ${contribution.title} ${localize("contribution-title-post")}`; | ||
|
||
const newData = htmlTemplate | ||
.replace(/{{template-title}}/g, pageTitle) | ||
.replace(/{{template-description}}/g, localize("contribute-description")) | ||
.replace(/{{template-lang}}/g, language); | ||
|
||
return new Response(newData, { headers: { "content-type": "text/html; charset=utf-8" } }); | ||
} catch (error) { | ||
// @TODO-ZM: log error to sentry | ||
console.error(error); | ||
|
||
return new Response(notFound, { | ||
headers: { "content-type": "text/html; charset=utf-8" }, | ||
status: 404, | ||
}); | ||
} | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.