feat(backend): implement news fetching from git.cohabit

This commit is contained in:
Julien Oculi 2024-07-02 11:20:38 +02:00
parent ec90d92f46
commit 5593878c66
3 changed files with 119 additions and 1 deletions

View file

@ -37,7 +37,9 @@
"ux", "ux",
"route", "route",
"frontend", "frontend",
"components" "components",
"island",
"backend"
], ],
"[ignore]": { "[ignore]": {
"editor.defaultFormatter": "foxundermoon.shell-format" "editor.defaultFormatter": "foxundermoon.shell-format"

104
src/blog/mod.ts Normal file
View file

@ -0,0 +1,104 @@
import { BlogProps } from ':components/BlogCard.tsx'
import { NewsFrontMatter } from ':src/blog/types.ts'
import { base64ToString } from ':src/utils.ts'
import { extract } from '@std/front-matter/yaml'
export async function fetchNews(
publisher: string,
name: string,
): Promise<BlogProps> {
const apiUrl = 'https://git.cohabit.fr/api/v1/'
const baseEndpoint = new URL(`repos/${publisher}/.news/`, apiUrl)
const endpoint = new URL('contents/', baseEndpoint)
// Get readme content api url
const readmePath = encodeURIComponent(`${name}/README.md`)
const contentUrl = new URL(readmePath, endpoint)
// Fetch readme content, commit hash and raw url for relative links
const file = await getCommitAndContent(contentUrl)
// Get commit infos (author + date) and get readme content from base64 source
const { raw, url, lastUpdate, author } = await getAuthorAndParseContent(
file,
baseEndpoint,
)
// Extract frontmatter
const { attrs, body } = extract<NewsFrontMatter>(raw)
// Transform API responses into BlogProps for BlogCard and BlogPost components
return {
author,
publisher,
lastUpdate,
options: attrs['x-cohabit'],
title: attrs.title,
hash: file.sha,
description: attrs.description,
body,
name,
url,
tags: attrs.tags,
}
}
export async function* fetchNewsList(
publisher: string,
): AsyncGenerator<BlogProps, void, void> {
const apiUrl = 'https://git.cohabit.fr/api/v1/'
const baseEndpoint = new URL(`repos/${publisher}/.news/`, apiUrl)
const endpoint = new URL('contents/', baseEndpoint)
// Fetch repo content
const root = await fetch(endpoint).then((response) => response.json()) as {
name: string
type: string
}[]
// Fetch `README.md` in sub directories
const blogPropsList = root
// Remove file and dir starting with "."
.filter(isNewsDirectory)
// Fetch single news and return BlogProps
.map(({ name }) => fetchNews(publisher, name))
// Yield each news
for (const blogProps of blogPropsList) {
yield blogProps
}
}
async function getAuthorAndParseContent(
file: { download_url: string; content: string; last_commit_sha: string },
baseEndpoint: URL,
) {
const commitUrl = new URL(
`git/commits/${file.last_commit_sha}?stat=false&verification=false&files=false`,
baseEndpoint,
)
const infos = await fetch(commitUrl).then((response) =>
response.json()
) as {
created: string
author: { login: string }
}
return {
raw: base64ToString(file.content),
url: file.download_url,
lastUpdate: new Date(infos.created),
author: infos.author.login,
}
}
async function getCommitAndContent(contentUrl: URL) {
return await fetch(contentUrl).then((response) => response.json()) as {
download_url: string
content: string
sha: string
last_commit_sha: string
}
}
function isNewsDirectory(entry: { name: string; type: string }): boolean {
return entry.type === 'dir' && entry.name.startsWith('.') === false
}

12
src/blog/types.ts Normal file
View file

@ -0,0 +1,12 @@
export type NewsFrontMatter = {
title: string
description: string
tags?: string[]
'x-cohabit': {
links?: Record<string, string>[]
status: 'canceled' | 'futur' | 'current' | 'finished'
visibility?: 'public' | 'internal'
thumbnail: string
dueDate: string
}
}